import AlertInfo from '@/components/alert-info'; import ScatterChart from '@/components/echarts/scatter'; import HighlightCode from '@/components/highlight-code'; import IconFont from '@/components/icon-font'; import HotKeys, { KeyMap } from '@/config/hotkeys'; import useOverlayScroller from '@/hooks/use-overlay-scroller'; import useRequestToken from '@/hooks/use-request-token'; import { ClearOutlined, HolderOutlined, InfoCircleOutlined, PlusOutlined, SendOutlined } from '@ant-design/icons'; import { useIntl, useSearchParams } from '@umijs/max'; import { Button, Checkbox, Segmented, Spin, Tabs, Tooltip } from 'antd'; import classNames from 'classnames'; import _ from 'lodash'; import { PCA } from 'ml-pca'; import 'overlayscrollbars/overlayscrollbars.css'; import { Resizable } from 're-resizable'; import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { handleEmbedding } from '../apis'; import { ParamsSchema } from '../config/types'; import '../style/ground-left.less'; import '../style/rerank.less'; import { generateEmbeddingCode } from '../view-code/embedding'; import DynamicParams from './dynamic-params'; import FileList from './file-list'; import InputList from './input-list'; import ViewCommonCode from './view-common-code'; interface MessageProps { modelList: Global.BaseOption[]; loaded?: boolean; ref?: any; } const paramsConfig: ParamsSchema[] = []; const initialValues = {}; const GroundEmbedding: React.FC = forwardRef((props, ref) => { const { modelList } = props; const acceptType = '.txt, .doc, .docx, .xls, .xlsx, .csv, .md, .pdf, .eml, .msg, .ppt, .pptx, .xml, .epub, .html'; const messageId = useRef(0); const intl = useIntl(); const requestSource = useRequestToken(); const [searchParams] = useSearchParams(); const selectModel = searchParams.get('model') || ''; const [parameters, setParams] = useState({}); const [show, setShow] = useState(false); const [loading, setLoading] = useState(false); const [tokenResult, setTokenResult] = useState(null); const [collapse, setCollapse] = useState(false); const contentRef = useRef(''); const scroller = useRef(null); const inputListRef = useRef(null); const paramsRef = useRef(null); const messageListLengthCache = useRef(0); const requestToken = useRef(null); const [fileList, setFileList] = useState< { text: string; name: string; uid: number | string }[] >([]); const [outputType, setOutputType] = useState('chart'); const [outputHeight, setOutputHeight] = useState(180); const [embeddingData, setEmbeddingData] = useState<{ code: string; copyValue: string; }>({ code: '', copyValue: '' }); const [lessTwoInput, setLessTwoInput] = useState(false); const multiplePasteEnable = useRef(true); const [textList, setTextList] = useState< { text: string; uid: number | string; name: string }[] >([ { text: '', uid: -1, name: '' }, { text: '', uid: -2, name: '' } ]); const [scatterData, setScatterData] = useState([]); const resizeRef = useRef(null); const resizeMaxHeight = 400; const { initialize, updateScrollerPosition: updateDocumentScrollerPosition } = useOverlayScroller(); const { initialize: innitializeParams, updateScrollerPosition } = useOverlayScroller(); const formRef = useRef(null); useImperativeHandle(ref, () => { return { viewCode() { setShow(true); }, setCollapse() { setCollapse(!collapse); } }; }); const viewCodeContent = useMemo(() => { return generateEmbeddingCode({ api: '/v1-openai/embeddings', parameters: { ...parameters, input: [ ...textList.map((item) => item.text).filter((item) => item), ...fileList.map((item) => item.text).filter((item) => item) ] } }); }, [parameters, textList, fileList]); const inputEmpty = useMemo(() => { const list = [...textList, ...fileList]; return list.length < 2; }, [textList, fileList]); const generateEmbedding = useCallback( (embeddings: any[]) => { try { const dataList = embeddings.map((item) => { return item.embedding; }); const pca = new PCA(dataList); const pcadata = pca.predict(dataList, { nComponents: 2 }).to2DArray(); const input = [ ...textList.map((item) => item.text).filter((item) => item), ...fileList.map((item) => item.text).filter((item) => item) ]; const list = pcadata.map((item: number[], index: number) => { return { value: item, name: index + 1, text: input[index] }; }); setScatterData(list); const embeddingJson = embeddings.map((o, index) => { const item = _.cloneDeep(o); item.embedding = item.embedding.slice(0, 5); item.embedding.push(null); return item; }); setEmbeddingData({ code: JSON.stringify(embeddingJson, null, 2).replace(/null/g, '...'), copyValue: JSON.stringify(embeddings, null, 2) }); } catch (e) { console.log('error:', e); } }, [textList, fileList] ); const setMessageId = () => { messageId.current = messageId.current + 1; }; const handleStopConversation = () => { requestToken.current?.cancel?.(); setLoading(false); }; const submitMessage = async (current?: { role: string; content: string }) => { await formRef.current?.form.validateFields(); if (!parameters.model) return; try { const validTextList = textList.filter((item) => item.text); const validFileList = fileList.filter((item) => item.text); const inputList = [ ...validTextList.map((item) => item.text), ...validFileList.map((item) => item.text) ]; if (inputList.length < 2) { setLessTwoInput(true); return; } setTextList(validTextList); setFileList(validFileList); setLessTwoInput(false); setLoading(true); setMessageId(); setTokenResult(null); requestToken.current?.cancel?.(); requestToken.current = requestSource(); contentRef.current = current?.content || ''; const result: any = await handleEmbedding( { model: parameters.model, encoding_format: 'float', input: inputList }, { token: requestToken.current.token } ); setTokenResult(result.usage); const embeddingsList = result.data || []; generateEmbedding(embeddingsList); } catch (error: any) { setTokenResult({ error: true, errorMessage: error.response?.data?.error?.message }); } finally { setLoading(false); } }; const handleSendMessage = () => { submitMessage(); }; const handleCloseViewCode = () => { setShow(false); }; const handleUpdateFileList = ( files: { text: string; name: string; uid: number | string }[] ) => { console.log('files:', files); setFileList((preList) => { return [...preList, ...files]; }); }; const handleScaleOutputSize = ( e: any, direction: string, ref: any, d: any ) => { console.log('handleScaleOutputSize', e, direction, ref, d); if ( d.height + outputHeight <= resizeMaxHeight && d.height + outputHeight >= 180 ) { setOutputHeight(d.height + outputHeight); } }; const handleScaleResize = () => { console.log('handleScaleResize', resizeRef.current); const height = resizeRef.current?.state?.height; if (height) { setOutputHeight(height); } }; const handleDeleteFile = (uid: number | string) => { setFileList((preList) => { return preList.filter((item) => item.uid !== uid); }); }; const handleAddText = () => { inputListRef.current?.handleAdd(); }; const handleTextListChange = ( list: { text: string; uid: number | string; name: string }[] ) => { setTextList(list); }; const handleOnPaste = useCallback( (e: any, index: number) => { if (!multiplePasteEnable.current) return; const text = e.clipboardData.getData('text'); if (text) { const currentContent = textList[index].text; const dataLlist = text.split('\n').map((item: string) => { return { text: item?.trim(), uid: inputListRef.current?.setMessageId(), name: '' }; }); dataLlist[0].text = currentContent + dataLlist[0].text; const result = [ ...textList.slice(0, index), ...dataLlist, ...textList.slice(index + 1) ] .filter((item) => item.text) .map((item, index) => { return { ...item, uid: inputListRef.current?.setMessageId() }; }); setTextList(result); } }, [textList] ); const handleClearDocuments = () => { setTextList([ { text: '', uid: -1, name: '' }, { text: '', uid: -2, name: '' } ]); setFileList([]); setScatterData([]); setTokenResult(null); setLessTwoInput(false); setEmbeddingData({ code: '', copyValue: '' }); }; const handleOutputTypeChange = (value: string) => { setOutputType(value); }; const outputItems = useMemo(() => { return [ { key: 'chart', label: 'Chart', children: ( ) }, { key: 'json', label: 'JSON', children: (
) } ]; }, [outputHeight, scatterData, embeddingData]); useEffect(() => { setMessageId(); setScatterData([]); setTokenResult(null); }, [parameters.model]); useHotkeys( HotKeys.SUBMIT, (e: any) => { e.preventDefault(); handleSendMessage(); }, { enabled: !loading, preventDefault: true } ); useEffect(() => { if (scroller.current) { initialize(scroller.current); } }, [scroller.current, initialize]); useEffect(() => { if (paramsRef.current) { innitializeParams(paramsRef.current); } }, [paramsRef.current, innitializeParams]); useEffect(() => { if (textList.length + fileList.length > messageListLengthCache.current) { updateDocumentScrollerPosition(); } messageListLengthCache.current = textList.length + fileList.length; }, [textList.length, fileList.length]); return (

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

{ multiplePasteEnable.current = e.target.checked; }} > {intl.formatMessage({ id: 'playground.input.multiplePaste' })} {!loading ? ( [{KeyMap.SUBMIT.textKeybinding}]{' '} {intl.formatMessage({ id: 'common.button.submit' })} } > ) : ( )}
{lessTwoInput && ( )}

{intl.formatMessage({ id: 'playground.embedding.output' })} 1. {intl.formatMessage({ id: 'playground.embedding.pcatips1' })} 2.{' '} {intl.formatMessage({ id: 'playground.embedding.pcatips2' })} } >

{loading && (
)} ) }} maxHeight={resizeMaxHeight} minHeight={180} onResize={handleScaleResize} onResizeStop={handleScaleOutputSize} >
<>} items={outputItems} >
); }); export default memo(GroundEmbedding);