fix: row-textarea upload img

This commit is contained in:
jialin
2026-03-26 17:59:33 +08:00
committed by jialin
parent c74118e913
commit 08330e0b82
6 changed files with 163 additions and 116 deletions
+74 -59
View File
@@ -81,9 +81,6 @@ const RowTextarea: React.FC<SystemMessageProps> = (props) => {
onChange?.(e); onChange?.(e);
}; };
const handleClear = () => {
onChange?.({ target: { value: '' } });
};
const handleOnPaste = (e: any) => { const handleOnPaste = (e: any) => {
props.onPaste?.(e); props.onPaste?.(e);
}; };
@@ -119,20 +116,41 @@ const RowTextarea: React.FC<SystemMessageProps> = (props) => {
updateUidCount: () => `img-${Date.now()}` updateUidCount: () => `img-${Date.now()}`
}); });
const handleClear = () => {
onChange?.({ target: { value: '' } });
onDeleteImage?.([]);
};
const handleDeleteImage = (uid: number) => { const handleDeleteImage = (uid: number) => {
const updatedImgs = (data.imgs || []).filter((img) => img.uid !== uid); const updatedImgs = (data.imgs || []).filter((img) => img.uid !== uid);
onDeleteImage?.(updatedImgs); onDeleteImage?.(updatedImgs);
}; };
const expanded = autoSize.focus || !!data.imgs?.length || isFromUrl; const renderTextContent = () => {
if (data.imgs?.length || isFromUrl) {
return null;
}
console.log('expanded===========', expanded); return (
<>
{label && <span className="title">{label}</span>}
{data.content || (
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>
{placeholder}
</span>
)}
</>
);
};
const expanded = autoSize.focus && !isFromUrl && !data.imgs?.length;
return ( return (
<div <div
className={classNames('row-textarea-wrapper', { className={classNames('row-textarea-wrapper', {
dropDownOpen: dropDownOpen, dropDownOpen: dropDownOpen,
expanded: expanded expanded: expanded,
'from-url': isFromUrl
})} })}
> >
<div <div
@@ -141,51 +159,45 @@ const RowTextarea: React.FC<SystemMessageProps> = (props) => {
})} })}
style={{ ...style }} style={{ ...style }}
> >
<div {!data.imgs?.length && (
style={{ <div
display: expanded ? 'block' : 'none'
}}
className="textarea-wrapper"
>
{label && <span className="textarea-label">{label}</span>}
{!!data.imgs?.length && (
<div style={{ padding: 8 }}>
<ThumbImg
editable
dataList={data.imgs}
onDelete={handleDeleteImage}
/>
</div>
)}
<Input.TextArea
className="custome-scrollbar"
allowClear
ref={rowTextAreaRef}
placeholder={placeholder}
style={{ style={{
borderRadius: '0', display: expanded ? 'block' : 'none'
border: 'none',
width: '100%',
boxShadow: 'none'
}} }}
value={data.content} className="textarea-wrapper"
autoSize={ >
expanded {label && <span className="textarea-label">{label}</span>}
? { {
minRows: 5, <Input.TextArea
maxRows: 5 className="custome-scrollbar"
} allowClear
: { minRows: autoSize.minRows, maxRows: autoSize.maxRows } ref={rowTextAreaRef}
placeholder={placeholder}
style={{
borderRadius: '0',
border: 'none',
width: '100%',
boxShadow: 'none'
}}
value={data.content}
autoSize={
expanded
? {
minRows: 5,
maxRows: 5
}
: { minRows: autoSize.minRows, maxRows: autoSize.maxRows }
}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={handleOnChange}
onSelect={handleOnSelect}
onPaste={handleOnPaste}
onClear={handleClear}
></Input.TextArea>
} }
onFocus={handleFocus} </div>
onBlur={handleBlur} )}
onChange={handleOnChange}
onSelect={handleOnSelect}
onPaste={handleOnPaste}
onClear={handleClear}
></Input.TextArea>
</div>
{!expanded && ( {!expanded && (
<div <div
@@ -194,16 +206,19 @@ const RowTextarea: React.FC<SystemMessageProps> = (props) => {
})} })}
onClick={handleFocus} onClick={handleFocus}
> >
{ <div className="content" style={{ height: height }}>
<div className="content" style={{ height: height }}> {renderTextContent()}
{label && <span className="title">{label}</span>} {!!data.imgs?.length && (
{data.content || ( <div className="flex-center">
<span style={{ color: 'var(--ant-color-text-tertiary)' }}> {label && <span className="title">{label}</span>}
{placeholder} <ThumbImg
</span> editable
)} dataList={data.imgs}
</div> onDelete={handleDeleteImage}
} />
</div>
)}
</div>
</div> </div>
)} )}
<div <div
@@ -213,7 +228,7 @@ const RowTextarea: React.FC<SystemMessageProps> = (props) => {
> >
{ImageURLInput} {ImageURLInput}
<div className={'actions'}> <div className={'actions'}>
{!expanded && data.content && ( {!expanded && (data.content || !!data.imgs?.length) && (
<SmallCloseButton onClick={handleClear}></SmallCloseButton> <SmallCloseButton onClick={handleClear}></SmallCloseButton>
)} )}
{UploadImageButton} {UploadImageButton}
@@ -14,6 +14,7 @@
} }
.actions { .actions {
display: flex;
gap: 4px; gap: 4px;
align-items: center; align-items: center;
border-radius: var(--ant-border-radius); border-radius: var(--ant-border-radius);
@@ -37,6 +38,12 @@
} }
} }
&.from-url {
.actions-wrapper {
display: flex;
}
}
&.expanded { &.expanded {
.actions-wrapper { .actions-wrapper {
display: flex; display: flex;
+2 -2
View File
@@ -41,7 +41,7 @@ export const rerankerQuery = async (
model: string; model: string;
query: string; query: string;
top_n: number; top_n: number;
documents: string[]; documents: any[];
}, },
options?: any options?: any
) => { ) => {
@@ -57,7 +57,7 @@ export const handleEmbedding = async (
model: string; model: string;
encoding_format?: string; encoding_format?: string;
dimensions?: number; dimensions?: number;
input: string[]; input?: any[];
messages?: { messages?: {
role: string; role: string;
content: Array<{ content: Array<{
+7 -1
View File
@@ -308,7 +308,13 @@ const GroundEmbedding: React.FC<MessageProps> = forwardRef((props, ref) => {
newList[index] = { newList[index] = {
...current, ...current,
content: '', content: '',
imgs: list imgs: list.map((item) => {
return {
...item,
width: 32,
height: 32
};
})
}; };
} }
return newList; return newList;
+3 -1
View File
@@ -19,6 +19,7 @@ const ImgInputWrapper = styled.div`
right: 8px; right: 8px;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
border-radius: var(--ant-border-radius);
background: var(--ant-color-bg-container); background: var(--ant-color-bg-container);
} }
&:hover { &:hover {
@@ -85,7 +86,8 @@ const useAddImage = (options: {
} }
}; };
const handleClose = () => { const handleClose = (e?: any) => {
e?.stopPropagation();
setIsFromUrl(false); setIsFromUrl(false);
setOpenImgTips(false); setOpenImgTips(false);
}; };
+70 -53
View File
@@ -26,7 +26,7 @@ import InputList from '../components/input-list';
import RightContainer from '../components/right-container'; import RightContainer from '../components/right-container';
import TokenUsage from '../components/token-usage'; import TokenUsage from '../components/token-usage';
import ViewCommonCode from '../components/view-common-code'; import ViewCommonCode from '../components/view-common-code';
import { extractErrorMessage } from '../config'; import { extractErrorMessage, generateMessagesByListContent } from '../config';
import { rerankerSamples } from '../config/samples'; import { rerankerSamples } from '../config/samples';
import { LLM_METAKEYS } from '../hooks/config'; import { LLM_METAKEYS } from '../hooks/config';
import { useInitLLmMeta } from '../hooks/use-init-llm'; import { useInitLLmMeta } from '../hooks/use-init-llm';
@@ -72,42 +72,32 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
const requestToken = useRef<any>(null); const requestToken = useRef<any>(null);
const multiplePasteEnable = useRef<boolean>(true); const multiplePasteEnable = useRef<boolean>(true);
const [isEmptyText, setIsEmptyText] = useState<boolean>(false); const [isEmptyText, setIsEmptyText] = useState<boolean>(false);
const [fileList, setFileList] = useState<
{
text: string;
name: string;
uid: number | string;
score?: number;
showExtra?: boolean;
percent?: number;
rank?: number;
}[]
>([]);
const [isEmptyQuery, setIsEmptyQuery] = useState<boolean>(false); const [isEmptyQuery, setIsEmptyQuery] = useState<boolean>(false);
const [textList, setTextList] = useState< const [textList, setTextList] = useState<
{ {
text: string; content: string;
uid: number | string; uid: number | string;
name: string; name: string;
score?: number; score?: number;
showExtra?: boolean; showExtra?: boolean;
dataUrl?: string; imgs?: { uid: number | string; dataUrl: string }[];
percent?: number; percent?: number;
rank?: number; rank?: number;
role: string;
}[] }[]
>([ >([
{ {
text: '', content: '',
uid: -1, uid: -1,
name: '', name: '',
dataUrl: '' role: 'user'
}, },
{ {
text: '', content: '',
uid: -2, uid: -2,
name: '', name: '',
dataUrl: '' role: 'user'
} }
]); ]);
const [queryValue, setQueryValue] = useState<string>(''); const [queryValue, setQueryValue] = useState<string>('');
@@ -152,9 +142,10 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
if (sample) { if (sample) {
setTextList( setTextList(
sample.documents.map((item: string, index: number) => ({ sample.documents.map((item: string, index: number) => ({
text: item, content: item,
uid: setMessageId(), uid: setMessageId(),
name: `Document ${index + 1}`, name: `Document ${index + 1}`,
role: 'user',
percent: undefined, percent: undefined,
score: undefined, score: undefined,
rank: undefined rank: undefined
@@ -165,18 +156,40 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
} }
}, []); }, []);
const formatDocuments = (
list: {
content: string;
imgs?: { uid: number | string; dataUrl: string }[];
uid: number | string;
name: string;
role?: string;
}[]
) => {
const validTextList = list.filter(
(item) => item.content || item.imgs?.length
);
const hasImages = validTextList.some((item) => item.imgs?.length);
if (hasImages) {
return generateMessagesByListContent(validTextList, true).map(
(msg: any) => ({ content: msg.content })
);
}
return validTextList.map((item) => item.content || '');
};
const viewCodeContent = useMemo(() => { const viewCodeContent = useMemo(() => {
return generateRerankCode({ return generateRerankCode({
api: RERANKER_API, api: RERANKER_API,
parameters: { parameters: {
...parameters, ...parameters,
query: queryValue, query: queryValue,
documents: [...textList, ...fileList] documents: formatDocuments(textList as any)
.map((item) => item.text)
.filter((text) => text)
} }
}); });
}, [parameters, queryValue, textList, fileList]); }, [parameters, queryValue, textList]);
// [0.1, 1.0] // [0.1, 1.0]
const normalizValue = (data: { min: number; max: number; value: number }) => { const normalizValue = (data: { min: number; max: number; value: number }) => {
@@ -232,9 +245,11 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
await formRef.current?.form.validateFields(); await formRef.current?.form.validateFields();
if (!parameters.model || !queryValue) return; if (!parameters.model || !queryValue) return;
const documentList: any[] = [...textList, ...fileList]; const documentList: any[] = [...textList];
const validDocus = documentList.filter((item) => item.text); const validDocus = documentList.filter(
(item) => item.content || item.imgs?.length
);
if (!validDocus.length) { if (!validDocus.length) {
setIsEmptyText(true); setIsEmptyText(true);
@@ -247,7 +262,9 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
requestToken.current?.cancel?.(); requestToken.current?.cancel?.();
requestToken.current = requestSource(); requestToken.current = requestSource();
const filledList = textList.filter((item) => item.text); const filledList = textList.filter(
(item) => item.content || item.imgs?.length
);
setTextList(filledList); setTextList(filledList);
@@ -256,7 +273,7 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
model: parameters.model, model: parameters.model,
top_n: parameters.top_n, top_n: parameters.top_n,
query: query, query: query,
documents: filledList.map((item) => item.text) documents: formatDocuments(filledList)
}, },
{ {
token: requestToken.current.token token: requestToken.current.token
@@ -332,7 +349,7 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
}; };
const handleTextListChange = ( const handleTextListChange = (
list: { text: string; uid: number | string; name: string }[] list: { content: string; uid: number | string; name: string }[]
) => { ) => {
const newList = list?.map((item: any) => { const newList = list?.map((item: any) => {
item.percent = undefined; item.percent = undefined;
@@ -357,35 +374,33 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
list: { uid: number | string; dataUrl: string }[], list: { uid: number | string; dataUrl: string }[],
index: number index: number
) => { ) => {
// replace the text with dataUrl in the textList
setTextList((preList) => { setTextList((preList) => {
const newList = [...preList]; const newList = [...preList];
const current = newList[index]; const current = newList[index];
if (current) { if (current) {
newList[index] = { newList[index] = {
...current, ...current,
text: '', content: '',
uid: list[0].uid, imgs: list.map((item) => ({
dataUrl: list[0].dataUrl ...item,
width: 32,
height: 32
}))
}; };
} }
return newList; return newList;
}); });
}; };
const handleOnDeleteImage = (item: { const handleOnDeleteImage = (
text: string; itemUid: number | string,
uid: number | string; updatedImgs: { uid: number | string; dataUrl: string }[]
name: string; ) => {
dataUrl?: string;
}) => {
// replace the dataUrl with empty text in the textList
setTextList((preList) => { setTextList((preList) => {
const newList = [...preList]; const newList = [...preList];
const current = newList.find((i) => i.uid === item.uid); const current = newList.find((i) => i.uid === itemUid);
if (current) { if (current) {
current.text = ''; current.imgs = updatedImgs;
current.dataUrl = '';
} }
return newList; return newList;
}); });
@@ -397,20 +412,21 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
if (text) { if (text) {
const dataLlist = text.split('\n').map((item: string) => { const dataLlist = text.split('\n').map((item: string) => {
return { return {
text: item?.trim(), content: item?.trim(),
name: '', name: '',
uid: setMessageId(), uid: setMessageId(),
role: 'user',
percent: undefined, percent: undefined,
score: undefined, score: undefined,
rank: undefined rank: undefined
}; };
}); });
dataLlist[0].text = `${selectionTextRef.current?.beforeText || ''}${dataLlist[0].text}${selectionTextRef.current?.afterText || ''}`; dataLlist[0].content = `${selectionTextRef.current?.beforeText || ''}${dataLlist[0].content}${selectionTextRef.current?.afterText || ''}`;
const result = [ const result = [
...textList.slice(0, index), ...textList.slice(0, index),
...dataLlist, ...dataLlist,
...textList.slice(index + 1) ...textList.slice(index + 1)
].filter((item) => item.text); ].filter((item) => item.content || item.imgs?.length);
setTextList(result); setTextList(result);
} }
@@ -419,17 +435,18 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
const handleClearDocuments = () => { const handleClearDocuments = () => {
setTextList([ setTextList([
{ {
text: '', content: '',
uid: setMessageId(), uid: setMessageId(),
name: '' name: '',
role: 'user'
}, },
{ {
text: '', content: '',
uid: setMessageId(), uid: setMessageId(),
name: '' name: '',
role: 'user'
} }
]); ]);
setFileList([]);
setTokenResult(null); setTokenResult(null);
setIsEmptyText(false); setIsEmptyText(false);
}; };
@@ -448,11 +465,11 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
}, [initialize]); }, [initialize]);
useEffect(() => { useEffect(() => {
if (textList.length + fileList.length > messageListLengthCache.current) { if (textList.length > messageListLengthCache.current) {
updateDocumentScrollerPosition(); updateDocumentScrollerPosition();
} }
messageListLengthCache.current = textList.length + fileList.length; messageListLengthCache.current = textList.length;
}, [textList.length, fileList.length]); }, [textList.length]);
return ( return (
<div className="ground-left-wrapper rerank"> <div className="ground-left-wrapper rerank">