diff --git a/src/components/logs-viewer/index.tsx b/src/components/logs-viewer/index.tsx index 70beec8b..d8894591 100644 --- a/src/components/logs-viewer/index.tsx +++ b/src/components/logs-viewer/index.tsx @@ -3,7 +3,7 @@ import useOverlayScroller from '@/hooks/use-overlay-scroller'; import '@xterm/xterm/css/xterm.css'; import classNames from 'classnames'; import _ from 'lodash'; -import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { replaceLineRegex } from './config'; import useParseAnsi from './parse-ansi'; import './styles/index.less'; @@ -92,13 +92,6 @@ const LogsViewer: React.FC = (props) => { updateScrollerPosition(0); }, 200); - const copyText = useMemo(() => { - if (!logs.length) { - return ''; - } - return logs?.map((item) => item.content).join('\n'); - }, [logs]); - useEffect(() => { createChunkConnection(); return () => { diff --git a/src/components/logs-viewer/logs-list.tsx b/src/components/logs-viewer/logs-list.tsx index f23acdee..9d0b3b48 100644 --- a/src/components/logs-viewer/logs-list.tsx +++ b/src/components/logs-viewer/logs-list.tsx @@ -14,7 +14,7 @@ import './styles/logs-list.less'; interface LogsListProps { dataList: any[]; height?: number; - onScroll?: (isTop: boolean) => void; + onScroll?: (data: { isTop: boolean; isBottom: boolean }) => void; diffHeight?: number; ref?: any; } @@ -61,13 +61,26 @@ const LogsList: React.FC = forwardRef((props, ref) => { const scrollTop = scrollEventElement?.scrollTop; const scrollHeight = scrollEventElement?.scrollHeight; const clientHeight = scrollEventElement?.clientHeight; - // is scroll to bottom + stopScroll.current = scrollTop + clientHeight <= scrollHeight; + // is scroll to bottom + const isBottom = scrollTop + clientHeight === scrollHeight; // is scroll to top if (scrollTop === 0) { - onScroll?.(true); + onScroll?.({ + isTop: true, + isBottom: false + }); + } else if (isBottom) { + onScroll?.({ + isTop: false, + isBottom: true + }); } else { - onScroll?.(false); + onScroll?.({ + isTop: false, + isBottom: false + }); } debounceResetStopScroll(); }, diff --git a/src/components/logs-viewer/virtual-log-list.tsx b/src/components/logs-viewer/virtual-log-list.tsx index 61e9cb32..b5f6f666 100644 --- a/src/components/logs-viewer/virtual-log-list.tsx +++ b/src/components/logs-viewer/virtual-log-list.tsx @@ -42,6 +42,7 @@ const LogsViewer: React.FC = forwardRef((props, ref) => { const [isAtTop, setIsAtTop] = useState(false); const [scrollPos, setScrollPos] = useState([]); const logListRef = useRef(null); + const loadMoreDone = useRef(false); useImperativeHandle(ref, () => ({ abort() { @@ -171,18 +172,28 @@ const LogsViewer: React.FC = forwardRef((props, ref) => { }; const handleOnScroll = useCallback( - async (isTop: boolean) => { + async (data: { isTop: boolean; isBottom: boolean }) => { + const { isTop, isBottom } = data; setIsAtTop(isTop); - if (loading || isLoadend || logs.length < pageSize || !enableScorllLoad) { + if ( + loading || + (logs.length > 0 && logs.length < pageSize && !loadMoreDone.current) || + !enableScorllLoad + ) { return; } - if (isTop && !isLoadend) { + + if (isTop && !loadMoreDone.current) { tail.current = undefined; createChunkConnection(); - setIsLoadend(true); + loadMoreDone.current = true; + } else if (isTop && page <= totalPage && page > 1) { + getPrePage(); + } else if (isBottom && page < totalPage) { + getNextPage(); } }, - [loading, isLoadend, logs.length, pageSize, enableScorllLoad] + [loading, logs.length, pageSize, enableScorllLoad, page, totalPage] ); const debouncedScroll = useCallback( @@ -204,9 +215,9 @@ const LogsViewer: React.FC = forwardRef((props, ref) => { }; }, [url, props.params]); - useEffect(() => { - debouncedScroll(); - }, [scrollPos]); + // useEffect(() => { + // debouncedScroll(); + // }, [scrollPos]); return (
diff --git a/src/components/version-info/index.less b/src/components/version-info/index.less index 1033526f..c68d0e0f 100644 --- a/src/components/version-info/index.less +++ b/src/components/version-info/index.less @@ -52,7 +52,7 @@ } } - .upgrade { + .upgrade-text { display: flex; justify-content: space-between; padding-block: 20px 4px; diff --git a/src/components/version-info/index.tsx b/src/components/version-info/index.tsx index caf84eee..c433901b 100644 --- a/src/components/version-info/index.tsx +++ b/src/components/version-info/index.tsx @@ -52,7 +52,7 @@ const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => { )}
{getAtomStorage(userAtom)?.is_admin && isProd && ( -
+
{latestVersion && latestVersion !== currentVersion && diff --git a/src/hooks/use-table-row-selection.ts b/src/hooks/use-table-row-selection.ts index 9276c84e..79e92ec6 100644 --- a/src/hooks/use-table-row-selection.ts +++ b/src/hooks/use-table-row-selection.ts @@ -11,14 +11,23 @@ export default function useTableRowSelection() { setSelectedRowKeys([]); }; - const removeSelectedKey = (rowKey: React.Key) => { + const removeSelectedKey = (rowKey: React.Key | React.Key[]) => { + if (Array.isArray(rowKey)) { + setSelectedRowKeys((keys) => keys.filter((key) => !rowKey.includes(key))); + return; + } setSelectedRowKeys((keys) => keys.filter((key) => key !== rowKey)); }; + const removeSelectedKeys = (rowKeys: React.Key[]) => { + setSelectedRowKeys((keys) => keys.filter((key) => !rowKeys.includes(key))); + }; + const rowSelection = { selectedRowKeys, clearSelections, onChange: onSelectChange, + removeSelectedKeys, removeSelectedKey }; diff --git a/src/hooks/use-update-chunk-list.ts b/src/hooks/use-update-chunk-list.ts index 62272d45..73becf24 100644 --- a/src/hooks/use-update-chunk-list.ts +++ b/src/hooks/use-update-chunk-list.ts @@ -2,7 +2,7 @@ import { WatchEventType } from '@/config'; import { useEffect, useRef } from 'react'; interface ChunkedCollection { - ids: string[]; + ids: string[] | number[]; data: any; collection: any[]; type: string | number; @@ -11,12 +11,13 @@ interface ChunkedCollection { export function useUpdateChunkedList(options: { dataList?: any[]; limit?: number; - setDataList: (args: any) => void; + setDataList: (args: any, opts?: any) => void; callback?: (args: any) => void; filterFun?: (args: any) => boolean; mapFun?: (args: any) => any; computedID?: (d: object) => string; }) { + const deletedIdsRef = useRef>(new Set()); const cacheDataListRef = useRef(options.dataList || []); const timerRef = useRef(null); const countRef = useRef(0); @@ -26,6 +27,9 @@ export function useUpdateChunkedList(options: { cacheDataListRef.current = [...(options.dataList || [])]; }, [options.dataList]); + const setDeletedIds = (ids: number[]) => { + deletedIdsRef.current = new Set([...deletedIdsRef.current, ...ids]); + }; const debounceUpdateChunckedList = () => { clearTimeout(timerRef.current); timerRef.current = setTimeout(() => { @@ -51,7 +55,7 @@ export function useUpdateChunkedList(options: { if (options?.mapFun) { collections = data?.collection?.map(options?.mapFun); } - const ids = data?.ids || []; + const ids: any[] = data?.ids || []; // CREATE if (data?.type === WatchEventType.CREATE) { const newDataList = collections.reduce((acc: any[], item: any) => { @@ -79,7 +83,10 @@ export function useUpdateChunkedList(options: { return !ids?.includes(item.id); } ); - options.setDataList?.([...cacheDataListRef.current]); + setDeletedIds(ids as number[]); + options.setDataList?.([...cacheDataListRef.current], { + deletedIds: [...deletedIdsRef.current] + }); } // UPDATE if (data?.type === WatchEventType.UPDATE) { @@ -106,7 +113,8 @@ export function useUpdateChunkedList(options: { }; return { updateChunkedList, - cacheDataListRef + cacheDataListRef, + deletedIdsRef }; } diff --git a/src/locales/en-US/playground.ts b/src/locales/en-US/playground.ts index 375ba02f..429bd92c 100644 --- a/src/locales/en-US/playground.ts +++ b/src/locales/en-US/playground.ts @@ -134,5 +134,7 @@ export default { 'playground.image.edit.tips': 'Click or drag image to this area to upload', 'playground.image.saveMask': 'Save Mask', 'playground.image.brushSize': 'Brush Size', - 'playground.image.download': 'Download Image' + 'playground.image.download': 'Download Image', + 'playground.image.generate': 'Generate', + 'playground.image.edit': 'Edit' }; diff --git a/src/locales/zh-CN/playground.ts b/src/locales/zh-CN/playground.ts index da9e4b41..1a6eff35 100644 --- a/src/locales/zh-CN/playground.ts +++ b/src/locales/zh-CN/playground.ts @@ -129,5 +129,7 @@ export default { 'playground.image.edit.tips': '点击或拖动图片到此区域上传', 'playground.image.saveMask': '保存遮罩', 'playground.image.brushSize': '画笔大小', - 'playground.image.download': '下载图片' + 'playground.image.download': '下载图片', + 'playground.image.generate': '生成图片', + 'playground.image.edit': '编辑图片' }; diff --git a/src/pages/llmodels/components/instance-item.tsx b/src/pages/llmodels/components/instance-item.tsx index 41311393..4282d621 100644 --- a/src/pages/llmodels/components/instance-item.tsx +++ b/src/pages/llmodels/components/instance-item.tsx @@ -221,7 +221,8 @@ const InstanceItem: React.FC = ({ @@ -241,7 +242,8 @@ const InstanceItem: React.FC = ({ diff --git a/src/pages/llmodels/components/table-list.tsx b/src/pages/llmodels/components/table-list.tsx index 86aeb379..55d99a09 100644 --- a/src/pages/llmodels/components/table-list.tsx +++ b/src/pages/llmodels/components/table-list.tsx @@ -30,7 +30,7 @@ import { Access, useAccess, useIntl, useNavigate } from '@umijs/max'; import { Button, Dropdown, Input, Space, Tag, message } from 'antd'; import dayjs from 'dayjs'; import _ from 'lodash'; -import { memo, useCallback, useRef, useState } from 'react'; +import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { MODELS_API, @@ -66,6 +66,7 @@ interface ModelsProps { perPage: number; query?: string; }; + deleteIds?: number[]; gpuDeviceList: GPUDeviceItem[]; workerList: WorkerListItem[]; dataSource: ListItem[]; @@ -111,6 +112,7 @@ const Models: React.FC = ({ handleDeleteSuccess, onViewLogs, onCancelViewLogs, + deleteIds, dataSource, gpuDeviceList, workerList, @@ -211,6 +213,12 @@ const Models: React.FC = ({ } ); + useEffect(() => { + if (deleteIds?.length) { + rowSelection.removeSelectedKey(deleteIds); + } + }, [deleteIds]); + const sourceOptions = [ { label: 'Hugging Face', diff --git a/src/pages/llmodels/components/view-logs-modal.tsx b/src/pages/llmodels/components/view-logs-modal.tsx index 079af005..d35f1010 100644 --- a/src/pages/llmodels/components/view-logs-modal.tsx +++ b/src/pages/llmodels/components/view-logs-modal.tsx @@ -35,6 +35,7 @@ const ViewCodeModal: React.FC = (props) => { const updateHandler = (list: any) => { const data = list?.find((item: any) => item.data?.id === props.id); + // state in InstanceRealLogStatus will not enable scorll load, because it is in the trasisition state if (data) { setEnableScorllLoad( () => !InstanceRealLogStatus.includes(data?.data?.state) diff --git a/src/pages/llmodels/index.tsx b/src/pages/llmodels/index.tsx index e1a98d1f..e79b2beb 100644 --- a/src/pages/llmodels/index.tsx +++ b/src/pages/llmodels/index.tsx @@ -19,10 +19,12 @@ const Models: React.FC = () => { const [modelInstances, setModelInstances] = useState([]); const [dataSource, setDataSource] = useState<{ dataList: ListItem[]; + deletedIds: number[]; loading: boolean; total: number; }>({ dataList: [], + deletedIds: [], loading: false, total: 0 }); @@ -39,18 +41,20 @@ const Models: React.FC = () => { search: '' }); - const { updateChunkedList, cacheDataListRef } = useUpdateChunkedList({ - dataList: dataSource.dataList, - setDataList(list) { - setDataSource((pre) => { - return { - total: pre.total, - loading: false, - dataList: list - }; - }); - } - }); + const { updateChunkedList, cacheDataListRef, deletedIdsRef } = + useUpdateChunkedList({ + dataList: dataSource.dataList, + setDataList(list, opts?: any) { + setDataSource((pre) => { + return { + total: pre.total, + loading: false, + dataList: list, + deletedIds: opts?.deletedIds || [] + }; + }); + } + }); const getWorkerList = async () => { try { @@ -79,21 +83,23 @@ const Models: React.FC = () => { setDataSource({ dataList: res.items || [], loading: false, - total: res.pagination.total + total: res.pagination.total, + deletedIds: [] }); } catch (error) { if (!isPageHidden.current) { setDataSource({ dataList: [], loading: false, - total: dataSource.total + total: dataSource.total, + deletedIds: [] }); } console.log('error+++', error); } finally { setFirstLoad(false); } - }, [queryParams, firstLoad]); + }, [queryParams]); const handlePageChange = useCallback( (page: number, pageSize: number | undefined) => { @@ -110,6 +116,8 @@ const Models: React.FC = () => { _.each(list, (data: any) => { updateChunkedList(data); }); + + console.log('deletedIdsRef=======', deletedIdsRef.current); }; const updateInstanceHandler = (list: any) => { @@ -143,22 +151,10 @@ const Models: React.FC = () => { } }, []); - const handleSearch = useCallback( - (e: any) => { - fetchData(); - }, - [fetchData] - ); - - const handleNameChange = useCallback( - (e: any) => { - setQueryParams({ - ...queryParams, - search: e.target.value - }); - }, - [queryParams] - ); + const getList = async () => { + await fetchData(); + await createModelsChunkRequest(); + }; const handleOnViewLogs = useCallback(() => { isPageHidden.current = true; @@ -178,8 +174,24 @@ const Models: React.FC = () => { }, 100); }, [fetchData, createModelsChunkRequest, createModelsInstanceChunkRequest]); + const handleSearch = useCallback( + async (e: any) => { + await fetchData(); + }, + [fetchData] + ); + + const debounceUpdateFilter = _.debounce((e: any) => { + setQueryParams({ + ...queryParams, + search: e.target.value + }); + }, 350); + + const handleNameChange = useCallback(debounceUpdateFilter, [queryParams]); + useEffect(() => { - fetchData(); + getList(); return () => { axiosToken?.cancel?.(); }; @@ -187,6 +199,7 @@ const Models: React.FC = () => { useEffect(() => { getWorkerList(); + createModelsInstanceChunkRequest(); return () => { chunkRequedtRef.current?.current?.cancel?.(); @@ -195,15 +208,6 @@ const Models: React.FC = () => { }; }, []); - useEffect(() => { - if (!firstLoad) { - setTimeout(() => { - createModelsChunkRequest(); - createModelsInstanceChunkRequest(); - }, 100); - } - }, [firstLoad]); - useEffect(() => { const handleVisibilityChange = async () => { if (document.visibilityState === 'visible') { @@ -245,6 +249,7 @@ const Models: React.FC = () => { queryParams={queryParams} loading={dataSource.loading} total={dataSource.total} + deleteIds={dataSource.deletedIds} gpuDeviceList={gpuDeviceList} workerList={workerList} > diff --git a/src/pages/playground/components/ground-embedding.tsx b/src/pages/playground/components/ground-embedding.tsx index ccd55f52..7041a87d 100644 --- a/src/pages/playground/components/ground-embedding.tsx +++ b/src/pages/playground/components/ground-embedding.tsx @@ -13,7 +13,7 @@ import { SendOutlined } from '@ant-design/icons'; import { useIntl, useSearchParams } from '@umijs/max'; -import { Button, Checkbox, Segmented, Tabs, Tooltip } from 'antd'; +import { Button, Checkbox, Segmented, Spin, Tabs, Tooltip } from 'antd'; import classNames from 'classnames'; import _ from 'lodash'; import { PCA } from 'ml-pca'; @@ -600,6 +600,23 @@ const GroundEmbedding: React.FC = forwardRef((props, ref) => { >
+ {loading && ( +
+ +
+ )} = forwardRef((props, ref) => { }; const updateCacheFormData = (values: Record) => { - cacheFormData.current = { - ...cacheFormData.current, - ...values - }; + _.merge(cacheFormData.current, values); }; const handleRandomPrompt = useCallback(() => { @@ -555,10 +552,7 @@ const GroundImages: React.FC = forwardRef((props, ref) => { if (!isOpenaiCompatible) { setParams((pre: object) => { - return { - ...pre, - ..._.pick(model?.meta, METAKEYS, {}) - }; + return _.merge({}, pre, _.pick(model?.meta, METAKEYS, {})); }); form.current?.form?.setFieldsValue({ ..._.pick(model?.meta, METAKEYS, {}) diff --git a/src/pages/playground/components/params-settings.tsx b/src/pages/playground/components/params-settings.tsx index 7ff6f7cf..6d7eea94 100644 --- a/src/pages/playground/components/params-settings.tsx +++ b/src/pages/playground/components/params-settings.tsx @@ -118,14 +118,13 @@ const ParamsSettings: React.FC = ({ model = model || _.get(modelList, '[0].value'); } const modelMetaData = handleModelChange(model); + const mergeData = _.merge({}, initialValues, modelMetaData); form.setFieldsValue({ - ...initialValues, - ...modelMetaData, + ...mergeData, model: model }); setParams({ - ...initialValues, - ...modelMetaData, + ...mergeData, model: model }); }, [modelList, showModelSelector, selectedModel]); diff --git a/src/pages/playground/components/test.txt b/src/pages/playground/components/test.txt deleted file mode 100644 index 47d15d48..00000000 --- a/src/pages/playground/components/test.txt +++ /dev/null @@ -1,46 +0,0 @@ -Organic skincare for sensitive skin with aloe vera and chamomile: Imagine the soothing embrace of nature with our organic skincare range, crafted specifically for sensitive skin. Infused with the calming properties of aloe vera and chamomile, each product provides gentle nourishment and protection. Say goodbye to irritation and hello to a glowing, healthy complexion. - -New makeup trends focus on bold colors and innovative techniques: Step into the world of cutting-edge beauty with this seasons makeup trends. Bold, vibrant colors and groundbreaking techniques are redefining the art of makeup. From neon eyeliners to holographic highlighters, unleash your creativity and make a statement with every look. - -Bio-Hautpflege für empfindliche Haut mit Aloe Vera und Kamille: Erleben Sie die wohltuende Wirkung unserer Bio-Hautpflege, speziell für empfindliche Haut entwickelt. Mit den beruhigenden Eigenschaften von Aloe Vera und Kamille pflegen und schützen unsere Produkte Ihre Haut auf natürliche Weise. Verabschieden Sie sich von Hautirritationen und genießen Sie einen strahlenden Teint. - -Neue Make-up-Trends setzen auf kräftige Farben und innovative Techniken: Tauchen Sie ein in die Welt der modernen Schönheit mit den neuesten Make-up-Trends. Kräftige, lebendige Farben und innovative Techniken setzen neue Maßstäbe. Von auffälligen Eyelinern bis hin zu holografischen Highlightern – lassen Sie Ihrer Kreativität freien Lauf und setzen Sie jedes Mal ein Statement. - -Cuidado de la piel orgánico para piel sensible con aloe vera y manzanilla: Descubre el poder de la naturaleza con nuestra línea de cuidado de la piel orgánico, diseñada especialmente para pieles sensibles. Enriquecidos con aloe vera y manzanilla, estos productos ofrecen una hidratación y protección suave. Despídete de las irritaciones y saluda a una piel radiante y saludable. - -针对敏感肌专门设计的天然有机护肤产品:体验由芦荟和洋甘菊提取物带来的自然呵护。我们的护肤产品特别为敏感肌设计,温和滋润,保护您的肌肤不受刺激。让您的肌肤告别不适,迎来健康光彩。 - -新的化妆趋势注重鲜艳的颜色和创新的技巧:进入化妆艺术的新纪元,本季的化妆趋势以大胆的颜色和创新的技巧为主。无论是霓虹眼线还是全息高光,每一款妆容都能让您脱颖而出,展现独特魅力。 - -新しいメイクのトレンドは鮮やかな色と革新的な技術に焦点を当てています: 今シーズンのメイクアップトレンドは、大胆な色彩と革新的な技術に注目しています。ネオンアイライナーからホログラフィックハイライターまで、クリエイティビティを解き放ち、毎回ユニークなルックを演出しましょう。 - -asdfas -dfaf -defsdf -aloe -fd -Farbensdf -asdfasdf -asdfasdfas -dfafas -dfafasdf -saludadf -1 -2 - -2 -2 -2 -2 -222 -2 - -2 -2 -2 -2 -2 - -2 -2 -2