diff --git a/web/screens/Thread/ThreadCenterPanel/ChatItem/index.tsx b/web/screens/Thread/ThreadCenterPanel/ChatItem/index.tsx index 92fd6394d..c7c05dca9 100644 --- a/web/screens/Thread/ThreadCenterPanel/ChatItem/index.tsx +++ b/web/screens/Thread/ThreadCenterPanel/ChatItem/index.tsx @@ -1,6 +1,7 @@ -import React, { forwardRef, useEffect, useState } from 'react' +import React, { forwardRef, useEffect, useMemo, useState } from 'react' import { + ContentType, events, MessageEvent, MessageStatus, @@ -8,9 +9,20 @@ import { ThreadMessage, } from '@janhq/core' +import { Tooltip } from '@janhq/joi' + +import { FolderOpenIcon } from 'lucide-react' + import ErrorMessage from '@/containers/ErrorMessage' -import SimpleTextMessage from '../SimpleTextMessage' +import { usePath } from '@/hooks/usePath' + +import { toGibibytes } from '@/utils/converter' +import { openFileTitle } from '@/utils/titleUtils' + +import Icon from '../FileUploadPreview/Icon' +import TextMessage from '../TextMessage' +import { RelativeImage } from '../TextMessage/RelativeImage' type Ref = HTMLDivElement @@ -27,6 +39,13 @@ const ChatItem = forwardRef((message, ref) => { ? message : undefined ) + const messageType = useMemo(() => content[0]?.type ?? '', [content]) + + const annotation = useMemo( + () => content[0]?.text?.annotations[0] ?? '', + [content] + ) + const { onViewFile, onViewFileContainer } = usePath() function onMessageUpdate(data: ThreadMessage) { if (data.id === message.id) { @@ -54,7 +73,60 @@ const ChatItem = forwardRef((message, ref) => { <> {status !== MessageStatus.Error && content?.length > 0 && (
- + {messageType === ContentType.Image && ( +
+
+ onViewFile(annotation)} + /> +
+ + +
+ } + content={{openFileTitle()}} + /> +
+ )} + + {messageType === ContentType.Pdf && ( +
+
onViewFile(`${message.id}.${messageType}`)} + /> + + +
+ } + content={{openFileTitle()}} + /> + +
+
+ {content[0].text.name?.replaceAll(/[-._]/g, ' ')} +
+

+ {toGibibytes(Number(content[0].text.size))} +

+
+
+ )} + {messageType === ContentType.Text && ( + + )} )} {errorMessage && !message.loadModelError && ( diff --git a/web/screens/Thread/ThreadCenterPanel/SimpleTextMessage/index.tsx b/web/screens/Thread/ThreadCenterPanel/TextMessage/MarkdownTextMessage.tsx similarity index 50% rename from web/screens/Thread/ThreadCenterPanel/SimpleTextMessage/index.tsx rename to web/screens/Thread/ThreadCenterPanel/TextMessage/MarkdownTextMessage.tsx index a8d2cdbf2..e9a6bba0f 100644 --- a/web/screens/Thread/ThreadCenterPanel/SimpleTextMessage/index.tsx +++ b/web/screens/Thread/ThreadCenterPanel/TextMessage/MarkdownTextMessage.tsx @@ -1,217 +1,26 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable @typescript-eslint/naming-convention */ -import React, { memo, useMemo } from 'react' +import React, { memo } from 'react' import Markdown from 'react-markdown' -import { ChatCompletionRole, ContentType, ThreadMessage } from '@janhq/core' - -import { Tooltip } from '@janhq/joi' - import latex from 'highlight.js/lib/languages/latex' -import { useAtomValue } from 'jotai' -import { FolderOpenIcon } from 'lucide-react' import rehypeHighlight from 'rehype-highlight' import rehypeHighlightCodeLines from 'rehype-highlight-code-lines' import rehypeKatex from 'rehype-katex' import rehypeRaw from 'rehype-raw' import remarkMath from 'remark-math' import 'katex/dist/katex.min.css' -import { twMerge } from 'tailwind-merge' - -import LogoMark from '@/containers/Brand/Logo/Mark' import { useClipboard } from '@/hooks/useClipboard' -import { usePath } from '@/hooks/usePath' import { getLanguageFromExtension } from '@/utils/codeLanguageExtension' -import { toGibibytes } from '@/utils/converter' -import { displayDate } from '@/utils/datetime' - -import { openFileTitle } from '@/utils/titleUtils' - -import EditChatInput from '../EditChatInput' -import Icon from '../FileUploadPreview/Icon' -import MessageToolbar from '../MessageToolbar' - -import { RelativeImage } from './RelativeImage' - -import { - editMessageAtom, - getCurrentChatMessagesAtom, - tokenSpeedAtom, -} from '@/helpers/atoms/ChatMessage.atom' -import { activeThreadAtom } from '@/helpers/atoms/Thread.atom' - -const SimpleTextMessage: React.FC = (props) => { - const isUser = props.role === ChatCompletionRole.User - const isSystem = props.role === ChatCompletionRole.System - const editMessage = useAtomValue(editMessageAtom) - const activeThread = useAtomValue(activeThreadAtom) - - const { onViewFile, onViewFileContainer } = usePath() - const tokenSpeed = useAtomValue(tokenSpeedAtom) - const messages = useAtomValue(getCurrentChatMessagesAtom) - - const text = useMemo( - () => props.content[0]?.text?.value ?? '', - [props.content] - ) - const messageType = useMemo( - () => props.content[0]?.type ?? '', - [props.content] - ) - - const annotation = useMemo( - () => props.content[0]?.text?.annotations[0] ?? '', - [props.content] - ) - - return ( -
-
- {!isUser && !isSystem && } - {isUser && ( -
- - - -
- )} - -
- {isUser - ? props.role - : (activeThread?.assistants[0].assistant_name ?? props.role)} -
-

- {displayDate(props.created)} -

-
- -
- {tokenSpeed && - tokenSpeed.message === props.id && - tokenSpeed.tokenSpeed > 0 && ( -

- Token Speed: {Number(tokenSpeed.tokenSpeed).toFixed(2)}t/s -

- )} -
- -
- <> - {messageType === ContentType.Image && ( -
-
- onViewFile(annotation)} - /> -
- - -
- } - content={{openFileTitle()}} - /> -
- )} - - {messageType === ContentType.Pdf && ( -
-
onViewFile(`${props.id}.${messageType}`)} - /> - - -
- } - content={{openFileTitle()}} - /> - -
-
- {props.content[0].text.name?.replaceAll(/[-._]/g, ' ')} -
-

- {toGibibytes(Number(props.content[0].text.size))} -

-
-
- )} - - {editMessage === props.id ? ( -
- -
- ) : ( -
- -
- )} - -
- - ) -} export const MarkdownTextMessage = memo( ({ text, id }: { id: string; text: string }) => { - console.log('rerender', id) const clipboard = useClipboard({ timeout: 1000 }) + console.log('rerender', id) function extractCodeLines(node: { children: { children: any[] }[] }) { const codeLines: any[] = [] @@ -316,15 +125,15 @@ export const MarkdownTextMessage = memo( const button = event.currentTarget as HTMLElement button.innerHTML = ` - - Copied - ` + + Copied + ` setTimeout(() => { button.innerHTML = ` - - Copy - ` + + Copy + ` }, 2000) }, }, @@ -412,5 +221,3 @@ export const MarkdownTextMessage = memo( ) } ) - -export default React.memo(SimpleTextMessage) diff --git a/web/screens/Thread/ThreadCenterPanel/SimpleTextMessage/RelativeImage.tsx b/web/screens/Thread/ThreadCenterPanel/TextMessage/RelativeImage.tsx similarity index 100% rename from web/screens/Thread/ThreadCenterPanel/SimpleTextMessage/RelativeImage.tsx rename to web/screens/Thread/ThreadCenterPanel/TextMessage/RelativeImage.tsx diff --git a/web/screens/Thread/ThreadCenterPanel/TextMessage/index.tsx b/web/screens/Thread/ThreadCenterPanel/TextMessage/index.tsx new file mode 100644 index 000000000..d5e9030d3 --- /dev/null +++ b/web/screens/Thread/ThreadCenterPanel/TextMessage/index.tsx @@ -0,0 +1,129 @@ +import React, { useMemo } from 'react' + +import { ChatCompletionRole, ContentType, ThreadMessage } from '@janhq/core' + +import { Tooltip } from '@janhq/joi' + +import { useAtomValue } from 'jotai' +import { FolderOpenIcon } from 'lucide-react' +import 'katex/dist/katex.min.css' +import { twMerge } from 'tailwind-merge' + +import LogoMark from '@/containers/Brand/Logo/Mark' + +import { usePath } from '@/hooks/usePath' + +import { displayDate } from '@/utils/datetime' + +import EditChatInput from '../EditChatInput' +import MessageToolbar from '../MessageToolbar' + +import { MarkdownTextMessage } from './MarkdownTextMessage' + +import { + editMessageAtom, + getCurrentChatMessagesAtom, + tokenSpeedAtom, +} from '@/helpers/atoms/ChatMessage.atom' +import { activeThreadAtom } from '@/helpers/atoms/Thread.atom' + +const TextMessage: React.FC = (props) => { + const isUser = props.role === ChatCompletionRole.User + const isSystem = props.role === ChatCompletionRole.System + const editMessage = useAtomValue(editMessageAtom) + const activeThread = useAtomValue(activeThreadAtom) + + const tokenSpeed = useAtomValue(tokenSpeedAtom) + const messages = useAtomValue(getCurrentChatMessagesAtom) + + const text = useMemo( + () => props.content[0]?.text?.value ?? '', + [props.content] + ) + + return ( +
+
+ {!isUser && !isSystem && } + {isUser && ( +
+ + + +
+ )} + +
+ {isUser + ? props.role + : (activeThread?.assistants[0].assistant_name ?? props.role)} +
+

+ {displayDate(props.created)} +

+
+ +
+ {tokenSpeed && + tokenSpeed.message === props.id && + tokenSpeed.tokenSpeed > 0 && ( +

+ Token Speed: {Number(tokenSpeed.tokenSpeed).toFixed(2)}t/s +

+ )} +
+ +
+ <> + {editMessage === props.id ? ( +
+ +
+ ) : ( +
+ +
+ )} + +
+
+ ) +} + +export default React.memo(TextMessage)