@@ -4,10 +4,53 @@ cursor: pointer; } +.messageContent { + &::selection, + & *::selection { + background-color: rgba(127, 125, 243, 0.35); + color: inherit; + } +} + +.selectionTooltipPopper { + &[data-popper-placement*='top'] .selectionTooltipArrow { + bottom: 0; + left: 0; + margin-bottom: -5px; + } + + &[data-popper-placement*='bottom'] .selectionTooltipArrow { + top: 0; + left: 0; + margin-top: -5px; + } +} + .selectionTooltip { + position: relative; display: flex; align-items: center; gap: 8px; + padding: 10px 13px; + background: #4b4b4b; + border-radius: 10px; + box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.04), 0px 4px 32px rgba(0, 0, 0, 0.16); +} + +.selectionTooltipArrow { + position: absolute; + width: 10px; + height: 10px; + pointer-events: none; + + &::before { + content: ''; + display: block; + width: 100%; + height: 100%; + background: #4b4b4b; + transform: rotate(45deg); + } } .selectionTooltipButton { @@ -1,5 +1,5 @@ -import React, { useCallback, useEffect, useRef } from 'react' -import { Box, Menu, MenuItem, Typography } from '@mui/material' +import React, { useCallback, useEffect, useMemo, useRef } from 'react' +import { Box, ClickAwayListener, Menu, MenuItem, Popper, Typography } from '@mui/material' import Stack from '@mui/material/Stack' import Image from 'next/image' @@ -13,8 +13,31 @@ import { FullscreenIcon } from '../icons/fullscreen-icon' import { FullscreenMessageModal } from '../fullscreen-message-modal/fullscreen-message-modal' import { ThinkBlock } from '../think-block/think-block' +type SelectionRect = { + top: number + left: number + width: number + height: number + bottom: number + right: number +} + type SelectionTooltipState = { text: string + rect: SelectionRect +} + +const SELECTION_TOOLTIP_Z_INDEX = 10050 + +function toSelectionRect(rect: DOMRect): SelectionRect { + return { + top: rect.top, + left: rect.left, + width: rect.width, + height: rect.height, + bottom: rect.bottom, + right: rect.right, + } } export function BotMessage(props: any) { @@ -24,6 +47,7 @@ export function BotMessage(props: any) { const [fullscreenModalOpen, setFullscreenModalOpen] = React.useState(false) const [selectionTooltip, setSelectionTooltip] = React.useState(null) const contentRef = useRef(null) + const [selectionArrowEl, setSelectionArrowEl] = React.useState(null) const ignoreClickAwayRef = useRef(false) const copy = (text: string) => { @@ -60,9 +84,25 @@ export function BotMessage(props: any) { return null } - return { text } + const rect = range.getBoundingClientRect() + if (!rect.width && !rect.height) { + return null + } + + return { text, rect: toSelectionRect(rect) } }, []) + const selectionVirtualAnchor = useMemo(() => { + if (!selectionTooltip) { + return null + } + const { rect } = selectionTooltip + return { + getBoundingClientRect: () => new DOMRect(rect.left, rect.top, rect.width, rect.height), + contextElement: contentRef.current ?? undefined, + } + }, [selectionTooltip]) + const showSelectionTooltip = useCallback(() => { const next = getSelectionInsideContent() if (next) { @@ -247,101 +287,128 @@ export function BotMessage(props: any) { - - - - - - ) : ( - '' - ) - } + { + if (props.message.file) { + window.open(props.message.file, '_blank') + } + }} + className={`${styles.messageContent} smallScroll`} + sx={{ + width: '100%', + minWidth: 0, + overflowY: 'auto', + overflowX: 'hidden', + position: 'relative', + padding: '0px 16px', + border: `1px solid #303035`, + color: '#f9fafb', + boxShadow: 'none', + lineHeight: '22.5px', + fontSize: '15px', + textAlign: 'left', + fontFamily: 'Inter,sans-serif', + borderRadius: '13px', + ...flexStyle, + }} > - { - if (props.message.file) { - window.open(props.message.file, '_blank') - } - }} - className='smallScroll' - sx={{ - width: '100%', - minWidth: 0, - overflowY: 'auto', - overflowX: 'hidden', - position: 'relative', - padding: '0px 16px', - border: `1px solid #303035`, - color: '#f9fafb', - boxShadow: 'none', - lineHeight: '22.5px', - fontSize: '15px', - textAlign: 'left', - fontFamily: 'Inter,sans-serif', - borderRadius: '13px', - ...flexStyle, - }} - > - {props.message.file ? ( - - - - ) : ( - '' - )} - {props.message.file ? ( - - ) : ( - <> - {thinkContents.map((thinkContent, index) => ( - - ))} - {displayMainContent ? ( - + - ) : null} - - )} - - + + ) : ( + '' + )} + {props.message.file ? ( + + ) : ( + <> + {thinkContents.map((thinkContent, index) => ( + + ))} + {displayMainContent ? ( + + ) : null} + + )} + + {selectionVirtualAnchor ? ( + +
+ +
e.preventDefault()} + > + + + + +
+
+
+
+ ) : null}