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

[FR-22] feature: handle image and text attachments in chat modal #2993

Merged
merged 1 commit into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"version": "24.09.0-alpha.1",
"private": true,
"dependencies": {
"@ai-sdk/openai": "^1.0.10",
"@ai-sdk/react": "^0.0.70",
"@ai-sdk/openai": "^1.0.11",
"@ai-sdk/react": "^1.0.7",
"@ant-design/cssinjs": "^1.22.0",
"@ant-design/icons": "^5.5.1",
"@ant-design/x": "^1.0.4",
Expand All @@ -23,7 +23,7 @@
"@uiw/codemirror-extensions-langs": "^4.23.6",
"@uiw/react-codemirror": "^4.23.6",
"ahooks": "^3.8.1",
"ai": "^4.0.20",
"ai": "^4.0.22",
"ansi_up": "^6.0.2",
"antd": "^5.22.2",
"antd-style": "^3.7.1",
Expand Down
427 changes: 239 additions & 188 deletions react/pnpm-lock.yaml

Large diffs are not rendered by default.

40 changes: 38 additions & 2 deletions react/src/components/lablupTalkativotUI/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import Flex from '../Flex';
// ES 2015
import ChatMessageContent from './ChatMessageContent';
import { Message } from '@ai-sdk/react';
import { Attachments } from '@ant-design/x';
import { useThrottle } from 'ahooks';
import { Avatar, theme } from 'antd';
import { Avatar, theme, Image } from 'antd';
import dayjs from 'dayjs';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import relativeTime from 'dayjs/plugin/relativeTime';
import _ from 'lodash';
import React from 'react';
import { useState } from 'react';

Expand All @@ -33,6 +35,7 @@ const ChatMessage: React.FC<{
const [isHovered, setIsHovered] = useState(false);

const throttledMessageContent = useThrottle(message.content, { wait: 50 });

return (
<Flex
direction={placement === 'left' ? 'row' : 'row-reverse'}
Expand Down Expand Up @@ -60,8 +63,41 @@ const ChatMessage: React.FC<{
align={placement === 'left' ? 'start' : 'end'}
wrap="wrap"
style={{ flex: 1 }}
gap={'xxs'}
gap={'xs'}
>
{_.map(message.experimental_attachments, (attachment, index) =>
_.includes(attachment?.contentType, 'image/') ? (
<Flex
style={{
border: 'none',
textAlign: 'end',
}}
align="end"
>
<Image
key={`${message?.id}-${index}`}
src={attachment?.url}
alt={attachment?.name}
style={{
maxWidth: '50vw',
maxHeight: '12vh',
borderRadius: token.borderRadius,
}}
/>
</Flex>
) : (
<Attachments.FileCard
key={index}
item={{
uid: `${message?.id}-${index}`,
name: attachment?.name || attachment?.url,
type: attachment?.contentType,
description: attachment?.name,
url: attachment?.url,
}}
/>
),
)}
<Flex
align="stretch"
direction="column"
Expand Down
119 changes: 99 additions & 20 deletions react/src/components/lablupTalkativotUI/LLMChatCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ import ModelSelect from './ModelSelect';
import VirtualChatMessageList from './VirtualChatMessageList';
import { createOpenAI } from '@ai-sdk/openai';
import { useChat } from '@ai-sdk/react';
import { DeleteOutlined, MoreOutlined } from '@ant-design/icons';
import {
CloudUploadOutlined,
DeleteOutlined,
LinkOutlined,
MoreOutlined,
} from '@ant-design/icons';
import { Attachments, AttachmentsProps, Sender } from '@ant-design/x';
import { useControllableValue } from 'ahooks';
import { streamText } from 'ai';
import {
Alert,
Badge,
Button,
Card,
CardProps,
Expand All @@ -25,7 +32,7 @@ import {
} from 'antd';
import _ from 'lodash';
import { Scale } from 'lucide-react';
import React, { useEffect, useRef } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';

export type BAIModel = {
Expand Down Expand Up @@ -81,6 +88,7 @@ const LLMChatCard: React.FC<LLMChatCardProps> = ({
...cardProps
}) => {
const webuiNavigate = useWebUINavigate();
const [isOpenAttachments, setIsOpenAttachments] = useState(false);

const [modelId, setModelId] = useControllableValue(cardProps, {
valuePropName: 'modelId',
Expand All @@ -89,16 +97,7 @@ const LLMChatCard: React.FC<LLMChatCardProps> = ({
});

const customModelFormRef = useRef<FormInstance>(null);

// const [userInput, setUserInput] = useControllableValue(cardProps,{
// valuePropName: "userInput",
// trigger: "onChangeUserInput",
// });

// useControllableValue(cardProps, {
// valuePropName: "agentId",
// trigger: "onAgentChange",
// });
const cardRef = useRef<HTMLDivElement>(null);

const {
messages,
Expand All @@ -109,7 +108,7 @@ const LLMChatCard: React.FC<LLMChatCardProps> = ({
isLoading,
append,
setMessages,
// ...chatHelpers
// ...chatHelpers,
} = useChat({
api: baseURL,
headers,
Expand Down Expand Up @@ -164,6 +163,8 @@ const LLMChatCard: React.FC<LLMChatCardProps> = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [submitKey]);

const [files, setFiles] = useState<AttachmentsProps['items']>([]);

const items: MenuProps['items'] = filterEmptyItem([
showCompareMenuItem && {
key: 'compare',
Expand Down Expand Up @@ -191,6 +192,7 @@ const LLMChatCard: React.FC<LLMChatCardProps> = ({

return (
<Card
ref={cardRef}
bordered
extra={
[
Expand Down Expand Up @@ -260,6 +262,65 @@ const LLMChatCard: React.FC<LLMChatCardProps> = ({
autoFocus
value={input}
placeholder="Say something..."
header={
<Sender.Header
closable={false}
title={t('chatui.Attachments')}
open={!!isOpenAttachments && !_.isEmpty(files)}
onOpenChange={setIsOpenAttachments}
styles={{
content: {
padding: 0,
},
}}
>
<Attachments
beforeUpload={() => false}
getDropContainer={() => cardRef.current}
accept="image/*,text/*"
items={files}
onChange={({ fileList }) => setFiles(fileList)}
placeholder={(type) =>
type === 'drop'
? {
title: t('chatui.DropFileHere'),
}
: {
icon: <CloudUploadOutlined />,
title: t('chatui.UploadFiles'),
description: t('chatui.UploadFilesDescription'),
}
}
/>
</Sender.Header>
}
prefix={
<Attachments
beforeUpload={() => false}
getDropContainer={() => cardRef.current}
accept="image/*,text/*"
items={files}
onChange={({ fileList }) => {
setFiles(fileList);
setIsOpenAttachments(true);
}}
placeholder={(type) =>
type === 'drop'
? {
title: t('chatui.DropFileHere'),
}
: {
icon: <CloudUploadOutlined />,
title: t('chatui.UploadFiles'),
description: t('chatui.UploadFilesDescription'),
}
}
>
<Badge dot={!_.isEmpty(files) && !isOpenAttachments}>
<Button type="text" icon={<LinkOutlined />} />
</Badge>
</Attachments>
}
onChange={(v: string) => {
setInput(v);
if (onInputChange) {
Expand All @@ -271,17 +332,35 @@ const LLMChatCard: React.FC<LLMChatCardProps> = ({
stop();
}}
onSend={() => {
if (input) {
append({
role: 'user',
content: input,
});
if (input || !_.isEmpty(files)) {
const fileList = _.map(
files,
(item) => item.originFileObj as File,
);
// Filter after converting to `File`
const fileListArray = _.filter(fileList, Boolean);
const dataTransfer = new DataTransfer();
_.forEach(fileListArray, (file) => dataTransfer.items.add(file));

append(
{
role: 'user',
content: input,
},
{
experimental_attachments: dataTransfer.files,
},
);

setTimeout(() => {
setInput('');
setFiles([]);
setIsOpenAttachments(false);
}, 0);
onSubmitChange?.();
}
}}
style={{ flex: 1 }}
/>,
]}
>
Expand Down Expand Up @@ -342,9 +421,9 @@ const LLMChatCard: React.FC<LLMChatCardProps> = ({
</Form>
</Flex>
{/* <ChatMessageList messages={messages} /> */}
{!_.isEmpty((error as any)?.responseBody) ? (
{!_.isEmpty(error?.message) ? (
<Alert
message={(error as any)?.responseBody}
message={error?.message}
type="error"
showIcon
style={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ScrollBottomHandlerButton from './ScrollBottomHandlerButton';
import { Message } from '@ai-sdk/react';
import { theme } from 'antd';
import Compact from 'antd/es/space/Compact';
import _ from 'lodash';
import React, { useRef, useState } from 'react';
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';

Expand Down Expand Up @@ -115,7 +116,7 @@ const VirtualChatMessageList: React.FC<VirtualizedListProps> = ({
}
}
}}
lastMessageContent={messages[messages.length - 1]?.content}
lastMessageContent={_.get(_.last(messages), 'content')}
/>
</div>
</Flex>
Expand Down
6 changes: 5 additions & 1 deletion resources/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -1743,7 +1743,11 @@
"DeleteChattingSessionDescription": "Sie sind dabei, dieses Thema zu löschen. \nEinmal gelöscht, kann es nicht wiederhergestellt werden. \nBitte gehen Sie vorsichtig vor.",
"SelectEndpoint": "Wählen Sie Endpunkt aus",
"SyncInput": "Eingang synchronisieren",
"CompareWithOtherModels": "Vergleichen Sie mit anderen Modellen"
"CompareWithOtherModels": "Vergleichen Sie mit anderen Modellen",
"Attachments": "Anhänge",
"DropFileHere": "Datei hier ablegen",
"UploadFiles": "Dateien hochladen",
"UploadFilesDescription": "Klicken oder ziehen Sie Dateien zum Hochladen in diesen Bereich"
},
"time": {
"ms": "ms",
Expand Down
6 changes: 5 additions & 1 deletion resources/i18n/el.json
Original file line number Diff line number Diff line change
Expand Up @@ -1743,7 +1743,11 @@
"DeleteChattingSessionDescription": "Πρόκειται να διαγράψετε αυτό το θέμα. \nΑφού διαγραφεί, δεν μπορεί να ανακτηθεί. \nΠαρακαλούμε προχωρήστε με προσοχή.",
"SelectEndpoint": "Επιλέξτε Τελικό σημείο",
"SyncInput": "Συγχρονισμός εισόδου",
"CompareWithOtherModels": "Συγκρίνετε με άλλα μοντέλα"
"CompareWithOtherModels": "Συγκρίνετε με άλλα μοντέλα",
"Attachments": "Συνημμένα",
"DropFileHere": "Αποθέστε το αρχείο εδώ",
"UploadFiles": "Μεταφόρτωση αρχείων",
"UploadFilesDescription": "Κάντε κλικ ή σύρετε αρχεία σε αυτήν την περιοχή για μεταφόρτωση"
},
"time": {
"ms": "ms",
Expand Down
6 changes: 5 additions & 1 deletion resources/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1747,7 +1747,11 @@
"DeleteChattingSession": "Delete chatting session",
"DeleteChattingSessionDescription": "You are about to delete this topic. Once deleted, it cannot be recovered. Please proceed with caution.",
"SyncInput": "Sync input",
"CompareWithOtherModels": "Compare with other models"
"CompareWithOtherModels": "Compare with other models",
"Attachments": "Attachments",
"DropFileHere": "Drop file here",
"UploadFiles": "Upload files",
"UploadFilesDescription": "Click or drag files to this area to upload"
},
"time": {
"ms": "ms",
Expand Down
6 changes: 5 additions & 1 deletion resources/i18n/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1745,7 +1745,11 @@
"DeleteChattingSessionDescription": "Estás a punto de eliminar este tema. \nUna vez eliminado, no se puede recuperar. \nProceda con precaución.",
"SelectEndpoint": "Seleccionar punto final",
"SyncInput": "Entrada de sincronización",
"CompareWithOtherModels": "Comparar con otros modelos"
"CompareWithOtherModels": "Comparar con otros modelos",
"Attachments": "Adjuntos",
"DropFileHere": "Suelta el archivo aquí",
"UploadFiles": "Subir archivos",
"UploadFilesDescription": "Haga clic o arrastre archivos a esta área para cargarlos"
},
"time": {
"ms": "ms",
Expand Down
6 changes: 5 additions & 1 deletion resources/i18n/fi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1742,7 +1742,11 @@
"DeleteChatHistory": "Poista keskusteluhistoria",
"SelectModel": "Valitse Malli",
"SyncInput": "Synkronoi sisääntulo",
"CompareWithOtherModels": "Vertaa muihin malleihin"
"CompareWithOtherModels": "Vertaa muihin malleihin",
"Attachments": "Liitteet",
"DropFileHere": "Pudota tiedosto tähän",
"UploadFiles": "Lataa tiedostoja",
"UploadFilesDescription": "Napsauta tai vedä tiedostoja tälle alueelle ladataksesi"
},
"time": {
"ms": "ms",
Expand Down
6 changes: 5 additions & 1 deletion resources/i18n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1743,7 +1743,11 @@
"DeleteChattingSessionDescription": "Vous êtes sur le point de supprimer ce sujet. \nUne fois supprimé, il ne peut pas être récupéré. \nVeuillez procéder avec prudence.",
"SelectEndpoint": "Sélectionnez le point de terminaison",
"SyncInput": "Entrée de synchronisation",
"CompareWithOtherModels": "Comparez avec d'autres modèles"
"CompareWithOtherModels": "Comparez avec d'autres modèles",
"Attachments": "Pièces jointes",
"DropFileHere": "Déposez le fichier ici",
"UploadFiles": "Télécharger des fichiers",
"UploadFilesDescription": "Cliquez ou faites glisser les fichiers vers cette zone pour les télécharger"
},
"time": {
"ms": "ms",
Expand Down
6 changes: 5 additions & 1 deletion resources/i18n/id.json
Original file line number Diff line number Diff line change
Expand Up @@ -1743,7 +1743,11 @@
"DeleteChattingSessionDescription": "Anda akan menghapus topik ini. \nSetelah dihapus, itu tidak dapat dipulihkan. \nSilakan lanjutkan dengan hati-hati.",
"SelectEndpoint": "Pilih Titik Akhir",
"SyncInput": "Sinkronkan masukan",
"CompareWithOtherModels": "Bandingkan dengan model lain"
"CompareWithOtherModels": "Bandingkan dengan model lain",
"Attachments": "Lampiran",
"DropFileHere": "Letakkan file di sini",
"UploadFiles": "Unggah file",
"UploadFilesDescription": "Klik atau seret file ke area ini untuk diunggah"
},
"time": {
"ms": "ms",
Expand Down
Loading
Loading