diff --git a/src/components/alert-info/index.tsx b/src/components/alert-info/index.tsx index 76892c31..644a7a49 100644 --- a/src/components/alert-info/index.tsx +++ b/src/components/alert-info/index.tsx @@ -46,4 +46,4 @@ const AlertInfo: React.FC = (props) => { ); }; -export default React.memo(AlertInfo); +export default AlertInfo; diff --git a/src/components/auto-image/single-image.tsx b/src/components/auto-image/single-image.tsx index c8eea73b..636cf02e 100644 --- a/src/components/auto-image/single-image.tsx +++ b/src/components/auto-image/single-image.tsx @@ -15,10 +15,10 @@ interface SingleImageProps { maxWidth?: number; dataUrl: string; label?: React.ReactNode; - uid: number; + uid: number | string; preview?: boolean; autoSize?: boolean; - onDelete: (uid: number) => void; + onDelete: (uid: number | string) => void; onClick?: (item: any) => void; autoBgColor?: boolean; editable?: boolean; @@ -94,7 +94,7 @@ const SingleImage: React.FC = (props) => { const handleOnLoad = React.useCallback(async () => {}, []); - const handleOnDelete = (uid: number, e: any) => { + const handleOnDelete = (uid: number | string, e: any) => { e.stopPropagation(); onDelete(uid); }; diff --git a/src/components/seal-form/row-textarea.tsx b/src/components/seal-form/row-textarea.tsx index 8f4338a6..e8c51c1e 100644 --- a/src/components/seal-form/row-textarea.tsx +++ b/src/components/seal-form/row-textarea.tsx @@ -1,4 +1,7 @@ -import { CloseOutlined } from '@ant-design/icons'; +import SmallCloseButton from '@/pages/_components/small-close-button'; +import ThumbImg from '@/pages/playground/components/thumb-img'; +import useAddImage from '@/pages/playground/hooks/use-add-image'; +import { DeleteOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Button, Input, Tooltip } from 'antd'; import classNames from 'classnames'; @@ -7,12 +10,20 @@ import './styles/row-textarea.less'; interface SystemMessageProps { style?: React.CSSProperties; - value: string; + data: { + text: string; + uid: number | string; + name: string; + dataUrl?: string; + }; placeholder?: string; label?: React.ReactNode; height?: number; + onUploadImage?: (list: { uid: number | string; dataUrl: string }[]) => void; + onDeleteImage?: () => void; onChange: (e: any) => void; onPaste?: (e: any) => void; + onDelete?: () => void; onSelect?: (data: { start: number; end: number; @@ -23,9 +34,12 @@ interface SystemMessageProps { const RowTextarea: React.FC = (props) => { const { - value, + data, + onDeleteImage, + onUploadImage, onChange, onSelect, + onDelete, style, label, placeholder, @@ -76,8 +90,8 @@ const RowTextarea: React.FC = (props) => { const start = e.target.selectionStart; const end = e.target.selectionEnd; - const beforeText = value.substring(0, start); - const afterText = value.substring(end, value.length); + const beforeText = data.text.substring(0, start); + const afterText = data.text.substring(end, data.text.length); onSelect?.({ start, end, @@ -86,64 +100,130 @@ const RowTextarea: React.FC = (props) => { }); }; + const handleUploadImage = ( + list: { uid: number | string; dataUrl: string }[] + ) => { + onUploadImage?.(list); + }; + + const { ImageURLInput, UploadImageButton, isFromUrl, dropDownOpen } = + useAddImage({ + size: 'small', + inputProps: { + variant: 'filled' + }, + handleUpdateImgList: handleUploadImage, + updateUidCount: () => `img-${Date.now()}` + }); + + const expanded = autoSize.focus || !!data.dataUrl || isFromUrl; + // const expanded = true; + + console.log('expanded===========', expanded); + return (
- { +
{label && {label}} + {data.dataUrl && ( +
+ +
+ )} +
- } - {!autoSize.focus && ( -
-
- {label && {label}} - {value || ( - - {placeholder} - - )} + + {!expanded && ( +
+ { +
+ {label && {label}} + {data.text || ( + + {placeholder} + + )} +
+ }
- {value && ( - + )} +
+ {ImageURLInput} +
+ {!expanded && data.text && ( + + )} + {UploadImageButton} + - )} +
- )} +
); }; diff --git a/src/components/seal-form/styles/row-textarea.less b/src/components/seal-form/styles/row-textarea.less index 66abf718..2705c6dd 100644 --- a/src/components/seal-form/styles/row-textarea.less +++ b/src/components/seal-form/styles/row-textarea.less @@ -1,8 +1,89 @@ -.row-textarea { +.row-textarea-wrapper { position: relative; - &.focus { - // padding-top: 9px; + .actions-wrapper { + display: none; + position: absolute; + right: 6px; + bottom: 50%; + transform: translateY(50%); + gap: 8px; + z-index: 10; + align-items: center; + justify-content: flex-end; + } + + .actions { + gap: 4px; + align-items: center; + border-radius: var(--ant-border-radius); + } + + &.show { + display: flex; + bottom: 8px; + transform: unset; + } + + &:hover { + .actions-wrapper { + display: flex; + } + } + + &.dropDownOpen { + .actions-wrapper { + display: flex; + } + } + + &.expanded { + .actions-wrapper { + display: flex; + bottom: 8px; + transform: unset; + } + } +} + +.row-textarea { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0; + overflow: hidden; + cursor: pointer; + transition: background-color 0.3s ease; + border-radius: var(--border-radius-base); + border: 1px solid var(--ant-color-border); + + .textarea-wrapper { + flex: 1; + width: 100%; + } + + &:hover { + &::before { + content: ''; + display: flex; + position: absolute; + left: 0; + bottom: 0; + right: 0; + top: 0; + background-color: var(--ant-color-fill-tertiary); + z-index: 5; + pointer-events: none; + } + } + + &:focus-within { + background-color: transparent; + + &::before { + background-color: transparent; + } } .content-wrap { @@ -10,13 +91,12 @@ display: flex; align-items: center; justify-content: space-between; - padding-right: 20px; + flex: 1; + width: 100%; cursor: pointer; - &:hover { - .clear-btn { - display: flex; - } + .content { + margin-right: 60px; } } @@ -25,14 +105,6 @@ box-shadow: none; } - .clear-btn { - display: none; - position: absolute; - right: 6px; - top: 50%; - transform: translateY(-50%); - } - .textarea-label { position: relative; top: 4px; @@ -45,7 +117,6 @@ height: 46px; line-height: 30px; padding: 8px 14px; - padding-right: 4px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; diff --git a/src/locales/lang-config-map.tsx b/src/locales/lang-config-map.tsx index e05206a2..2406d998 100644 --- a/src/locales/lang-config-map.tsx +++ b/src/locales/lang-config-map.tsx @@ -330,7 +330,6 @@ const langConfigMap = { title: '語言' } }; - export type LangConfigType = keyof typeof langConfigMap; export default langConfigMap; diff --git a/src/pages/_components/small-close-button.tsx b/src/pages/_components/small-close-button.tsx new file mode 100644 index 00000000..d074112f --- /dev/null +++ b/src/pages/_components/small-close-button.tsx @@ -0,0 +1,36 @@ +import { CloseCircleFilled } from '@ant-design/icons'; +import { Button } from 'antd'; +import styled from 'styled-components'; + +const StyledButton = styled(Button)` + background-color: transparent !important; + padding: 0; + .anticon { + color: var(--ant-color-text-quaternary); + font-size: 12px !important; + transition: color 0.3s ease; + } + &:hover { + .anticon { + color: var(--ant-color-text-tertiary); + } + } +`; + +interface SmallCloseButtonProps { + onClick?: () => void; +} + +const SmallCloseButton: React.FC = ({ onClick }) => { + return ( + } + shape="circle" + type="text" + onClick={onClick} + size="small" + > + ); +}; + +export default SmallCloseButton; diff --git a/src/pages/playground/components/ground-embedding.tsx b/src/pages/playground/components/ground-embedding.tsx index e4603e50..25b19c29 100644 --- a/src/pages/playground/components/ground-embedding.tsx +++ b/src/pages/playground/components/ground-embedding.tsx @@ -80,17 +80,19 @@ const GroundEmbedding: React.FC = forwardRef((props, ref) => { const selectionTextRef = useRef(null); const [textList, setTextList] = useState< - { text: string; uid: number | string; name: string }[] + { text: string; dataUrl?: string; uid: number | string; name: string }[] >([ { text: '', uid: -1, - name: '' + name: '', + dataUrl: '' }, { text: '', uid: -2, - name: '' + name: '', + dataUrl: '' } ]); @@ -169,12 +171,14 @@ const GroundEmbedding: React.FC = forwardRef((props, ref) => { try { await formRef.current?.form.validateFields(); if (!parameters.model) return; - const validTextList = textList.filter((item) => item.text); + const validTextList = textList.filter( + (item) => item.text || item.dataUrl + ); const validFileList = fileList.filter((item) => item.text); const inputList = [ - ...validTextList.map((item) => item.text), - ...validFileList.map((item) => item.text) + ...validTextList.map((item) => item.text || item.dataUrl || ''), + ...validFileList.map((item) => item.text || '') ]; if (inputList.length < 2) { @@ -280,6 +284,44 @@ const GroundEmbedding: React.FC = forwardRef((props, ref) => { setTextList(list); }; + const handleOnUploadImage = ( + list: { uid: number | string; dataUrl: string }[], + index: number + ) => { + // replace the text with dataUrl in the textList + setTextList((preList) => { + const newList = [...preList]; + const current = newList[index]; + if (current) { + newList[index] = { + ...current, + text: '', + uid: list[0].uid, + dataUrl: list[0].dataUrl + }; + } + return newList; + }); + }; + + const handleOnDeleteImage = (item: { + text: string; + uid: number | string; + name: string; + dataUrl?: string; + }) => { + // replace the dataUrl with empty text in the textList + setTextList((preList) => { + const newList = [...preList]; + const current = newList.find((i) => i.uid === item.uid); + if (current) { + current.text = ''; + current.dataUrl = ''; + } + return newList; + }); + }; + const handleonSelect = useCallback( (data: { start: number; @@ -540,6 +582,8 @@ const GroundEmbedding: React.FC = forwardRef((props, ref) => { onChange={handleTextListChange} onSelect={handleonSelect} onPaste={handleOnPaste} + onUploadImage={handleOnUploadImage} + onDeleteImage={handleOnDeleteImage} > {fileList.length > 0 && (
@@ -576,7 +620,7 @@ const GroundEmbedding: React.FC = forwardRef((props, ref) => { }} >

-
+
{intl.formatMessage({ id: 'playground.embedding.output' })} = forwardRef((props, ref) => {
diff --git a/src/pages/playground/components/ground-reranker.tsx b/src/pages/playground/components/ground-reranker.tsx index 2302b8e6..fc640148 100644 --- a/src/pages/playground/components/ground-reranker.tsx +++ b/src/pages/playground/components/ground-reranker.tsx @@ -122,6 +122,7 @@ const GroundReranker: React.FC = forwardRef((props, ref) => { name: string; score?: number; showExtra?: boolean; + dataUrl?: string; percent?: number; rank?: number; }[] @@ -129,12 +130,14 @@ const GroundReranker: React.FC = forwardRef((props, ref) => { { text: '', uid: -1, - name: '' + name: '', + dataUrl: '' }, { text: '', uid: -2, - name: '' + name: '', + dataUrl: '' } ]); const [queryValue, setQueryValue] = useState(''); @@ -388,6 +391,44 @@ const GroundReranker: React.FC = forwardRef((props, ref) => { selectionTextRef.current = data; }; + const handleOnUploadImage = ( + list: { uid: number | string; dataUrl: string }[], + index: number + ) => { + // replace the text with dataUrl in the textList + setTextList((preList) => { + const newList = [...preList]; + const current = newList[index]; + if (current) { + newList[index] = { + ...current, + text: '', + uid: list[0].uid, + dataUrl: list[0].dataUrl + }; + } + return newList; + }); + }; + + const handleOnDeleteImage = (item: { + text: string; + uid: number | string; + name: string; + dataUrl?: string; + }) => { + // replace the dataUrl with empty text in the textList + setTextList((preList) => { + const newList = [...preList]; + const current = newList.find((i) => i.uid === item.uid); + if (current) { + current.text = ''; + current.dataUrl = ''; + } + return newList; + }); + }; + const handleOnPaste = (e: any, index: number) => { if (!multiplePasteEnable.current) return; const text = e.clipboardData.getData('text'); @@ -559,7 +600,9 @@ const GroundReranker: React.FC = forwardRef((props, ref) => { onChange={handleTextListChange} extra={renderPercent} onSelect={handleonSelect} + onUploadImage={handleOnUploadImage} onPaste={handleOnPaste} + onDeleteImage={handleOnDeleteImage} > {isEmptyText && (
diff --git a/src/pages/playground/components/input-list.tsx b/src/pages/playground/components/input-list.tsx index c7be90e2..4663ec34 100644 --- a/src/pages/playground/components/input-list.tsx +++ b/src/pages/playground/components/input-list.tsx @@ -1,7 +1,5 @@ import RowTextarea from '@/components/seal-form/row-textarea'; -import { DeleteOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Button, Tooltip } from 'antd'; import React, { forwardRef, useImperativeHandle, useRef } from 'react'; import '../style/input-list.less'; @@ -14,11 +12,26 @@ interface InputListProps { text: string; uid: number | string; name: string; + dataUrl?: string; }[]; onChange?: ( - textList: { text: string; uid: number | string; name: string }[] + textList: { + text: string; + uid: number | string; + name: string; + dataUrl?: string; + }[] ) => void; onPaste?: (e: any, index: number) => void; + onDeleteImage?: (dataItem: { + text: string; + uid: number | string; + name: string; + }) => void; + onUploadImage?: ( + list: { uid: number | string; dataUrl: string }[], + index: number + ) => void; onSelect?: (data: { start: number; end: number; @@ -30,7 +43,17 @@ interface InputListProps { const InputList: React.FC = forwardRef( ( - { textList, showLabel = true, height, onChange, extra, onPaste, onSelect }, + { + textList, + showLabel = true, + height, + onUploadImage, + onDeleteImage, + onChange, + extra, + onPaste, + onSelect + }, ref ) => { const intl = useIntl(); @@ -53,19 +76,19 @@ const InputList: React.FC = forwardRef( onChange?.(dataList); }; - const handleDelete = (text: { text: string; uid: number | string }) => { + const handleDelete = (dataItem: { text: string; uid: number | string }) => { const dataList = [...textList]; - const index = dataList.findIndex((item) => item.uid === text.uid); + const index = dataList.findIndex((item) => item.uid === dataItem.uid); dataList.splice(index, 1); onChange?.(dataList); }; const handleTextChange = ( value: string, - text: { text: string; uid: number | string } + dataItem: { text: string; uid: number | string } ) => { const dataList = [...textList]; - const index = dataList.findIndex((item) => item.uid === text.uid); + const index = dataList.findIndex((item) => item.uid === dataItem.uid); dataList[index].text = value; onChange?.(dataList); }; @@ -79,36 +102,26 @@ const InputList: React.FC = forwardRef( return (
- {textList.map((text, index) => { + {textList.map((item, index) => { return ( -
+
handleTextChange(e.target.value, text)} + onChange={(e) => handleTextChange(e.target.value, item)} onPaste={(e) => onPaste?.(e, index)} onSelect={(data) => onSelect?.({ ...data, index })} + onUploadImage={(list) => onUploadImage?.(list, index)} + onDeleteImage={() => onDeleteImage?.(item)} + onDelete={() => handleDelete(item)} >
- - - - - - {extra?.(text)} + {extra?.(item)}
); })} diff --git a/src/pages/playground/hooks/use-add-image.tsx b/src/pages/playground/hooks/use-add-image.tsx index fbcacb19..f8c74172 100644 --- a/src/pages/playground/hooks/use-add-image.tsx +++ b/src/pages/playground/hooks/use-add-image.tsx @@ -30,15 +30,27 @@ const ImgInputWrapper = styled.div` const useAddImage = (options: { size?: 'small' | 'middle' | 'large'; + inputProps?: Record; handleUpdateImgList: ( list: { uid: number | string; dataUrl: string }[] ) => void; updateUidCount: () => number | string; }) => { - const { handleUpdateImgList, updateUidCount, size = 'middle' } = options; + const { + handleUpdateImgList, + updateUidCount, + size = 'middle', + inputProps + } = options; const intl = useIntl(); const [isFromUrl, setIsFromUrl] = useState(false); const [openImgTips, setOpenImgTips] = useState(false); + const [dropDownOpen, setDropDownOpen] = useState(false); + + const handleOnOpenChange = (open: boolean) => { + setDropDownOpen(open); + console.log('handleOnOpenChange', open); + }; const inputImgRef = useRef(null); const handleAddImgFromUrl = () => { @@ -93,6 +105,7 @@ const useAddImage = (options: { > - + ); return { isFromUrl, ImageURLInput, + dropDownOpen, UploadImageButton }; }; diff --git a/src/pages/playground/style/input-list.less b/src/pages/playground/style/input-list.less index 3598f7a4..6f88e8a0 100644 --- a/src/pages/playground/style/input-list.less +++ b/src/pages/playground/style/input-list.less @@ -1,53 +1,12 @@ .input-list { display: flex; flex-direction: column; - gap: 12px; + gap: 16px; .input-item { position: relative; - display: flex; - align-items: center; - justify-content: space-between; - padding: 0; - padding-right: 6px; - overflow: hidden; - cursor: pointer; - transition: background-color 0.3s ease; - border-radius: var(--border-radius-base); - border: 1px solid var(--ant-color-border); - - .btn-group { - display: none; - } - - &:hover { - //background-color: var(--ant-color-fill-tertiary); - &::before { - content: ''; - display: flex; - position: absolute; - left: 0; - bottom: 0; - right: 0; - top: 0; - background-color: var(--ant-color-fill-tertiary); - z-index: 5; - pointer-events: none; - } - - .btn-group { - display: flex; - gap: 8px; - } - - .hover-hidden { - opacity: 0; - } - } &:focus-within { - //background-color: transparent; - &::before { background-color: transparent; }