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 SealInputNumber from '@/components/seal-form/input-number'; import useOverlayScroller from '@/hooks/use-overlay-scroller'; import useRequestToken from '@/hooks/use-request-token'; import ResizeContainer from '@/pages/_components/terminal-tabs/resize-container'; import { ClearOutlined, PlusOutlined, QuestionCircleOutlined, SendOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; import { Button, Checkbox, Form, Segmented, Spin, Tabs, Tooltip } from 'antd'; import _ from 'lodash'; import 'overlayscrollbars/overlayscrollbars.css'; import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import { EMBEDDING_API, handleEmbedding } from '../apis'; import InputList from '../components/input-list'; import RightContainer from '../components/right-container'; import TokenUsage from '../components/token-usage'; import ViewCommonCode from '../components/view-common-code'; import { extractErrorMessage, generateMessagesByListContent } from '../config'; import { embeddingSamples } from '../config/samples'; import { LLM_METAKEYS } from '../hooks/config'; import useEmbeddingWorker from '../hooks/use-embedding-worker'; import { useInitLLmMeta } from '../hooks/use-init-llm'; import '../style/ground-llm.less'; import '../style/rerank.less'; import { generateEmbeddingCode } from '../view-code/embedding'; import DataForm from './forms'; interface MessageProps { modelList: Global.BaseOption[]; loaded?: boolean; ref?: any; } const GroundEmbedding: React.FC = forwardRef((props, ref) => { const { modelList } = props; const intl = useIntl(); const { workerRef, createWorker, postMessage, terminateWorker } = useEmbeddingWorker(); const requestSource = useRequestToken(); 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 messageListLengthCache = useRef(0); const requestToken = useRef(null); 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 selectionTextRef = useRef(null); const [textList, setTextList] = useState< { content: string; imgs?: { uid: number | string; dataUrl: string }[]; uid: number | string; name: string; role: string; }[] >([ { content: '', uid: -1, name: '', role: 'user' }, { content: '', uid: -2, name: '', role: 'user' } ]); const [scatterData, setScatterData] = useState([]); const resizeRef = useRef(null); const resizeMaxHeight = 400; const { initialize, updateScrollerPosition: updateDocumentScrollerPosition } = useOverlayScroller(); const { handleOnValuesChange, formRef, parameters, modelMeta } = useInitLLmMeta( { modelList, isChat: true }, { defaultValues: {}, defaultParamsConfig: [], metaKeys: LLM_METAKEYS } ); useImperativeHandle(ref, () => { return { viewCode() { setShow(true); }, setCollapse() { setCollapse(!collapse); }, calculateNewMaxFromBoundary: (maxWidth?: number, maxHeight?: number) => { resizeRef.current?.container?.calculateNewMaxFromBoundary(); }, collapse: collapse }; }); const formatInputs = ( list: { content: string; imgs?: { uid: number | string; dataUrl: string }[]; uid: number | string; name: string; role: string; }[] ) => { const validTextList = textList.filter( (item) => item.content || item.imgs?.length ); const hasImages = validTextList.some((item) => item.imgs?.length); const mutipleInput = generateMessagesByListContent(validTextList, true); // const firstInput = mutipleInput?.[0]; // mutipleInput.forEach((item: any) => { // firstInput.content = firstInput.content.concat(item.content); // }); return hasImages ? { messages: mutipleInput } : { input: [...validTextList.map((item) => item.content || '')] }; }; const viewCodeContent = useMemo(() => { return generateEmbeddingCode({ api: EMBEDDING_API, parameters: { ...parameters, ...formatInputs(textList) } }); }, [parameters, textList]); const inputEmpty = useMemo(() => { const list = [...textList]; return list.length < 2; }, [textList]); const setMessageId = () => { return inputListRef.current?.setMessageId?.(); }; const handleStopConversation = () => { requestToken.current?.cancel?.(); setLoading(false); }; const submitMessage = async (current?: { role: string; content: string }) => { try { await formRef.current?.form.validateFields(); if (!parameters.model) return; const validTextList = textList.filter( (item) => item.content || item.imgs?.length ); const inputList = formatInputs(textList); if (validTextList.length < 2) { setLessTwoInput(true); return; } setTextList(validTextList); 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', ...inputList }, { token: requestToken.current.token } ); setTokenResult(result.usage); const embeddingsList = result.data || []; createWorker(); workerRef.current!.onmessage = (event: MessageEvent) => { const { scatterData, embeddingData } = event.data; setScatterData(scatterData); setEmbeddingData(embeddingData); setLoading(false); }; postMessage({ embeddings: embeddingsList, textList: textList }); } catch (error: any) { setTokenResult({ error: true, errorMessage: extractErrorMessage(error.response) }); setLoading(false); } }; const handleSendMessage = () => { submitMessage(); }; const handleCloseViewCode = () => { setShow(false); }; 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 = () => { const height = resizeRef.current?.container?.state?.height; if (height) { setOutputHeight(height); } }; const handleAddText = () => { inputListRef.current?.handleAdd(); }; const handleTextListChange = ( list: { content: string; imgs?: { uid: number | string; dataUrl: string }[]; uid: number | string; name: string; role: string; }[] ) => { setTextList(list); }; const handleOnUploadImage = ( list: { uid: number | string; dataUrl: string }[], index: number ) => { setTextList((preList) => { const newList = [...preList]; const current = newList[index]; if (current) { newList[index] = { ...current, content: '', imgs: list }; } return newList; }); }; const handleOnDeleteImage = ( itemUid: number | string, updatedImgs: { uid: number | string; dataUrl: string }[] ) => { setTextList((preList) => { const newList = [...preList]; const current = newList.find((i) => i.uid === itemUid); if (current) { current.imgs = updatedImgs; } return newList; }); }; const handleonSelect = useCallback( (data: { start: number; end: number; beforeText: string; afterText: string; index: number; }) => { selectionTextRef.current = data; }, [] ); const handleOnPaste = useCallback( (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 { content: item?.trim(), name: '', uid: setMessageId(), role: 'user' }; }); dataLlist[0].content = `${selectionTextRef.current?.beforeText || ''}${dataLlist[0].content}${selectionTextRef.current?.afterText || ''}`; const result = [ ...textList.slice(0, index), ...dataLlist, ...textList.slice(index + 1) ].filter((item) => item.content || item.imgs?.length); setTextList(result); } }, [textList] ); const handleClearDocuments = () => { setTextList([ { content: '', uid: -1, name: '', role: 'user' }, { content: '', uid: -2, name: '', role: 'user' } ]); setScatterData([]); setTokenResult(null); setLessTwoInput(false); setEmbeddingData({ code: '', copyValue: '' }); }; const handleOutputTypeChange = (value: string) => { setOutputType(value); }; const renderExtra = useMemo(() => { if (modelMeta?.n_ctx && modelMeta?.n_slot) { return ( ); } return []; }, [modelMeta]); const outputItems = useMemo(() => { return [ { key: 'chart', label: 'Chart', children: ( ) }, { key: 'json', label: 'JSON', children: (
) } ]; }, [outputHeight, collapse, scatterData, embeddingData]); const onValuesChange = useMemoizedFn( (changeValues: Record, allValues: Record) => { if (changeValues.model) { setScatterData([]); setTokenResult(null); } handleOnValuesChange(changeValues, allValues); } ); useEffect(() => { if (scroller.current) { initialize(scroller.current); } }, [initialize]); useEffect(() => { if (textList.length > messageListLengthCache.current) { updateDocumentScrollerPosition(); } messageListLengthCache.current = textList.length; }, [textList.length]); useEffect(() => { if (intl.locale || 'en-US') { const sample = embeddingSamples[intl.locale]; if (sample) { setTextList( sample.map((item: string, index: number) => ({ content: item, uid: setMessageId(), name: `Document ${index + 1}`, role: 'user' })) ); } } }, []); return (

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

{ multiplePasteEnable.current = e.target.checked; }} > {intl.formatMessage({ id: 'playground.input.multiplePaste' })} {!loading ? ( {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 && (
)}
<>} items={outputItems} >
); }); export default GroundEmbedding;