diff --git a/react/src/components/lablupTalkativotUI/ChatMessage.tsx b/react/src/components/lablupTalkativotUI/ChatMessage.tsx index 4e198c0cdc..6f3104e68f 100644 --- a/react/src/components/lablupTalkativotUI/ChatMessage.tsx +++ b/react/src/components/lablupTalkativotUI/ChatMessage.tsx @@ -112,6 +112,7 @@ const ChatMessage: React.FC<{ message.role !== 'user' ? token.colorBgContainer : token.colorBgContainerDisabled, + maxWidth: '100%', }} > diff --git a/react/src/components/lablupTalkativotUI/LLMChatCard.tsx b/react/src/components/lablupTalkativotUI/LLMChatCard.tsx index aebf84021a..2d545c99c8 100644 --- a/react/src/components/lablupTalkativotUI/LLMChatCard.tsx +++ b/react/src/components/lablupTalkativotUI/LLMChatCard.tsx @@ -13,6 +13,7 @@ import { DeleteOutlined, LinkOutlined, MoreOutlined, + RocketOutlined, } from '@ant-design/icons'; import { Attachments, AttachmentsProps, Sender } from '@ant-design/x'; import { useControllableValue } from 'ahooks'; @@ -28,11 +29,13 @@ import { FormInstance, Input, MenuProps, + Tag, theme, + Typography, } from 'antd'; import _ from 'lodash'; import { Scale } from 'lucide-react'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; export type BAIModel = { @@ -137,6 +140,7 @@ const LLMChatCard: React.FC = ({ ), messages: body?.messages, }); + setStartTime(Date.now()); return result.toDataStreamResponse(); } else { return fetch(input, init); @@ -146,6 +150,8 @@ const LLMChatCard: React.FC = ({ const { token } = theme.useToken(); const { t } = useTranslation(); + const [startTime, setStartTime] = useState(null); + // If the `inputMessage` prop exists, the `input` state has to follow it. useEffect(() => { if (!_.isUndefined(inputMessage)) { @@ -163,6 +169,24 @@ const LLMChatCard: React.FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [submitKey]); + const totalTokens = useMemo(() => { + return ( + _.sumBy(messages, (message) => message?.content.length) + input.length + ); + }, [messages, input]); + + const tokenPerSecond = useMemo(() => { + if (!_.isEmpty(messages) && startTime) { + const lastMessage = _.last(messages); + if (lastMessage?.role === 'assistant') { + const lastToken = lastMessage.content?.length || 0; + const elapsedTime = (Date.now() - startTime) / 1000; + return lastToken / elapsedTime; + } + } + return 0; + }, [messages, startTime]); + const [files, setFiles] = useState([]); const items: MenuProps['items'] = filterEmptyItem([ @@ -340,7 +364,9 @@ const LLMChatCard: React.FC = ({ // Filter after converting to `File` const fileListArray = _.filter(fileList, Boolean); const dataTransfer = new DataTransfer(); - _.forEach(fileListArray, (file) => dataTransfer.items.add(file)); + _.forEach(fileListArray, (file) => { + dataTransfer.items.add(file); + }); append( { @@ -433,6 +459,21 @@ const LLMChatCard: React.FC = ({ /> ) : null} + + + + + + {tokenPerSecond.toFixed(2)} + tok/s + + + {totalTokens} + total tokens + + + + ); };