import AlertInfo from '@/components/alert-info'; import SealInputNumber from '@/components/seal-form/input-number'; import useOverlayScroller from '@/hooks/use-overlay-scroller'; import useRequestToken from '@/hooks/use-request-token'; import { ClearOutlined, PlusOutlined, QuestionCircleOutlined, SendOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Button, Checkbox, Form, Input, Spin, Tag, Tooltip, Typography } from 'antd'; import classNames from 'classnames'; import _ from 'lodash'; import 'overlayscrollbars/overlayscrollbars.css'; import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; import { RERANKER_API, rerankerQuery } from '../apis'; import { extractErrorMessage } from '../config'; import { rerankerSamples } from '../config/samples'; import { ParamsSchema } from '../config/types'; import { LLM_METAKEYS } from '../hooks/config'; import { useInitLLmMeta } from '../hooks/use-init-meta'; import '../style/ground-llm.less'; import '../style/rerank.less'; import '../style/system-message-wrap.less'; import { generateRerankCode } from '../view-code/rerank'; import DynamicParams from './dynamic-params'; import InputList from './input-list'; import TokenUsage from './token-usage'; import ViewCommonCode from './view-common-code'; const { Text } = Typography; const SearchInputWrapper = styled.div` margin: 16px 32px 10px; position: relative; `; const ValidText = styled(Text)` position: absolute; bottom: -20px; left: 0; `; interface MessageProps { modelList: Global.BaseOption[]; loaded?: boolean; ref?: any; } const fieldConfig: ParamsSchema[] = [ { type: 'InputNumber', name: 'top_n', label: { text: 'Top N', isLocalized: false }, attrs: { min: 1 }, rules: [ { required: true, message: 'Top N is required' } ] } ]; const GroundReranker: React.FC = forwardRef((props, ref) => { const { modelList } = props; const intl = useIntl(); const requestSource = useRequestToken(); const [show, setShow] = useState(false); const [loading, setLoading] = useState(false); const [tokenResult, setTokenResult] = useState(null); const [collapse, setCollapse] = useState(false); const scroller = useRef(null); const inputListRef = useRef(null); const messageListLengthCache = useRef(0); const requestToken = useRef(null); const multiplePasteEnable = useRef(true); const [isEmptyText, setIsEmptyText] = useState(false); const [fileList, setFileList] = useState< { text: string; name: string; uid: number | string; score?: number; showExtra?: boolean; percent?: number; rank?: number; }[] >([]); const [isEmptyQuery, setIsEmptyQuery] = useState(false); const [textList, setTextList] = useState< { text: string; uid: number | string; name: string; score?: number; showExtra?: boolean; percent?: number; rank?: number; }[] >([ { text: '', uid: -1, name: '' }, { text: '', uid: -2, name: '' } ]); const [queryValue, setQueryValue] = useState(''); const selectionTextRef = useRef(null); const { initialize, updateScrollerPosition: updateDocumentScrollerPosition } = useOverlayScroller(); const { handleOnValuesChange, formRef, paramsConfig, initialValues, parameters, paramsRef, modelMeta, formFields } = useInitLLmMeta( { modelList, isChat: true }, { defaultValues: { top_n: 3 }, defaultParamsConfig: fieldConfig, metaKeys: LLM_METAKEYS } ); useImperativeHandle(ref, () => { return { viewCode() { setShow(true); }, setCollapse() { setCollapse(!collapse); }, collapse: collapse }; }); const setMessageId = () => { const uid = inputListRef.current?.setMessageId(); return uid; }; useEffect(() => { if (intl.locale || 'en-US') { const sample = rerankerSamples[intl.locale]; if (sample) { setTextList( sample.documents.map((item: string, index: number) => ({ text: item, uid: setMessageId(), name: `Document ${index + 1}`, percent: undefined, score: undefined, rank: undefined })) ); setQueryValue(sample.query); } } }, []); const viewCodeContent = useMemo(() => { return generateRerankCode({ api: RERANKER_API, parameters: { ..._.pick(parameters, ['model', ..._.split(formFields, ',')]), query: queryValue, documents: [...textList, ...fileList] .map((item) => item.text) .filter((text) => text) } }); }, [parameters, formFields, queryValue, textList, fileList]); // [0.1, 1.0] const normalizValue = (data: { min: number; max: number; value: number }) => { const range = [0.5, 1.0]; const [a, b] = range; const { min, max, value } = data; if (isNaN(value) || isNaN(min) || isNaN(max) || min > max) { return 0; } if (min === max) { return 100; } const res = a + ((value - min) * (b - a)) / (max - min); return res * 100; }; const renderPercent = (data: any) => { if (!data.showExtra || !data.percent) { return null; } const percent = data.percent; return (
{intl.formatMessage({ id: 'playground.rerank.rank' })}: {data.rank} {intl.formatMessage({ id: 'playground.rerank.score' })}:{' '} {_.round(data.score, 2)}
); }; const submitMessage = async (query: string) => { try { setIsEmptyQuery(!queryValue); setTokenResult(null); await formRef.current?.form.validateFields(); if (!parameters.model || !queryValue) return; const documentList: any[] = [...textList, ...fileList]; const validDocus = documentList.filter((item) => item.text); if (!validDocus.length) { setIsEmptyText(true); return; } setIsEmptyText(false); setLoading(true); setMessageId(); requestToken.current?.cancel?.(); requestToken.current = requestSource(); const filledList = textList.filter((item) => item.text); setTextList(filledList); const result: any = await rerankerQuery( { model: parameters.model, top_n: parameters.top_n, query: query, documents: filledList.map((item) => item.text) }, { token: requestToken.current.token } ); setMessageId(); setTokenResult(result.usage); const sortList = _.sortBy( result.results || [], (item: any) => item.relevance_score ); const maxValue = sortList[sortList.length - 1].relevance_score; const minValue = sortList[0].relevance_score; // reset state let newTextList = filledList.map((item) => { item.percent = undefined; item.score = undefined; item.rank = undefined; return item; }); result.results?.forEach((item: any, sIndex: number) => { newTextList[item.index] = { ...newTextList[item.index], uid: setMessageId(), rank: sIndex + 1, score: item.relevance_score, showExtra: true, percent: normalizValue({ min: minValue, max: maxValue, value: item.relevance_score }) }; }); newTextList = _.sortBy(newTextList, 'rank'); setTextList(newTextList); } catch (error: any) { setTokenResult({ error: true, errorMessage: extractErrorMessage(error.response) }); } finally { setLoading(false); } }; const handleSearch = (val: string, event: any, action: any) => { if (action.source === 'clear') { return; } submitMessage(val); }; const handleQueryChange = (e: any) => { setQueryValue(e.target.value); setIsEmptyQuery(!e.target.value); }; const handleCloseViewCode = () => { setShow(false); }; const handleAddText = () => { inputListRef.current?.handleAdd(); }; const handleTextListChange = ( list: { text: string; uid: number | string; name: string }[] ) => { const newList = list?.map((item: any) => { item.percent = undefined; item.score = undefined; item.rank = undefined; return item; }); setTextList(newList); }; const handleonSelect = (data: { start: number; end: number; beforeText: string; afterText: string; index: number; }) => { selectionTextRef.current = data; }; const handleOnPaste = (e: any, index: number) => { if (!multiplePasteEnable.current) return; const text = e.clipboardData.getData('text'); if (text) { const dataLlist = text.split('\n').map((item: string) => { return { text: item?.trim(), name: '', uid: setMessageId(), percent: undefined, score: undefined, rank: undefined }; }); dataLlist[0].text = `${selectionTextRef.current?.beforeText || ''}${dataLlist[0].text}${selectionTextRef.current?.afterText || ''}`; const result = [ ...textList.slice(0, index), ...dataLlist, ...textList.slice(index + 1) ].filter((item) => item.text); setTextList(result); } }; const renderExtra = useMemo(() => { if (modelMeta?.n_ctx && modelMeta?.n_slot) { return ( ); } return null; }, [modelMeta]); const handleClearDocuments = () => { setTextList([ { text: '', uid: setMessageId(), name: '' }, { text: '', uid: setMessageId(), name: '' } ]); setFileList([]); setTokenResult(null); setIsEmptyText(false); }; const onValuesChange = useCallback((changedValues: any, allValues: any) => { if (changedValues.model) { setTokenResult(null); } handleOnValuesChange(changedValues, allValues); }, []); useEffect(() => { if (scroller.current) { initialize(scroller.current); } }, [initialize]); useEffect(() => { if (textList.length + fileList.length > messageListLengthCache.current) { updateDocumentScrollerPosition(); } messageListLengthCache.current = textList.length + fileList.length; }, [textList.length, fileList.length]); return (

{intl.formatMessage({ id: 'playground.rerank.query' })}

{intl.formatMessage({ id: 'common.button.submit' })} } > } placeholder={intl.formatMessage({ id: 'playground.rerank.query.holder' })} > {isEmptyQuery && ( {intl.formatMessage({ id: 'playground.rerank.query.validate' })} )}

{intl.formatMessage({ id: 'playground.embedding.documents' })}

{ multiplePasteEnable.current = e.target.checked; }} > {intl.formatMessage({ id: 'playground.input.multiplePaste' })}
{isEmptyText && (
)}
<>
{loading && (
)}
); }); export default GroundReranker;