diff --git a/src/components/auto-tooltip/index.tsx b/src/components/auto-tooltip/index.tsx index 41c03228..7f7ba87c 100644 --- a/src/components/auto-tooltip/index.tsx +++ b/src/components/auto-tooltip/index.tsx @@ -13,6 +13,7 @@ import React, { interface AutoTooltipProps extends Omit { children: React.ReactNode; maxWidth?: number | string; + minWidth?: number | string; color?: string; style?: React.CSSProperties; ghost?: boolean; @@ -24,6 +25,7 @@ interface AutoTooltipProps extends Omit { const AutoTooltip: React.FC = ({ children, maxWidth = '100%', + minWidth, ghost = false, title, showTitle = false, @@ -61,6 +63,7 @@ const AutoTooltip: React.FC = ({ const tagStyle = useMemo( () => ({ maxWidth, + minWidth, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' as const, diff --git a/src/components/logs-viewer/index.tsx b/src/components/logs-viewer/index.tsx index 918d43f5..00c92192 100644 --- a/src/components/logs-viewer/index.tsx +++ b/src/components/logs-viewer/index.tsx @@ -15,9 +15,10 @@ interface LogsViewerProps { } const LogsViewer: React.FC = (props) => { const { height, url } = props; - const { initialize, updateScrollerPosition } = useOverlayScroller({ - theme: 'os-theme-light' - }); + const { initialize, updateScrollerPosition, scrollEventElement } = + useOverlayScroller({ + theme: 'os-theme-light' + }); const { isClean, parseAnsi } = useParseAnsi(); const { setChunkFetch } = useSetChunkFetch(); const chunkRequedtRef = useRef(null); @@ -25,7 +26,6 @@ const LogsViewer: React.FC = (props) => { const cacheDataRef = useRef(''); const uidRef = useRef(0); const [logs, setLogs] = useState([]); - const autoScroll = useRef(true); const stopScroll = useRef(false); const setId = () => { @@ -33,10 +33,6 @@ const LogsViewer: React.FC = (props) => { return uidRef.current; }; - const clearScreen = () => { - cacheDataRef.current = ''; - }; - const updateContent = useCallback( (data: string) => { if (isClean(data)) { @@ -44,7 +40,7 @@ const LogsViewer: React.FC = (props) => { } else { cacheDataRef.current += data; } - const res = parseAnsi(cacheDataRef.current, setId, clearScreen); + const res = parseAnsi(cacheDataRef.current, setId); setLogs(res); }, [setLogs, setId] @@ -65,10 +61,17 @@ const LogsViewer: React.FC = (props) => { const debounceResetStopScroll = _.debounce(() => { stopScroll.current = false; - }, 3000); + }, 30000); const handleOnWheel = useCallback( (e: any) => { - stopScroll.current = true; + const scrollTop = scrollEventElement.scrollTop; + const scrollHeight = scrollEventElement.scrollHeight; + const clientHeight = scrollEventElement.clientHeight; + if (scrollTop + clientHeight >= scrollHeight) { + stopScroll.current = false; + } else { + stopScroll.current = true; + } debounceResetStopScroll(); }, [debounceResetStopScroll] @@ -91,7 +94,7 @@ const LogsViewer: React.FC = (props) => { if (logs.length && !stopScroll.current) { updateScrollerPosition(0); } - }, [logs, autoScroll.current, stopScroll.current]); + }, [logs, stopScroll.current]); return (
diff --git a/src/components/logs-viewer/parse-ansi.ts b/src/components/logs-viewer/parse-ansi.ts index e2c1440a..96ebeb52 100644 --- a/src/components/logs-viewer/parse-ansi.ts +++ b/src/components/logs-viewer/parse-ansi.ts @@ -17,138 +17,135 @@ const useParseAnsi = () => { return command === 'J' && n === 2; }, []); - const parseAnsi = useCallback( - (inputStr: string, setId: () => number, clearScreen: () => void) => { - let cursorRow = 0; // current row - let cursorCol = 0; // current column - // screen content array - let screen = [['']]; - // replace carriage return and newline characters in the text - let input = inputStr.replace(/\r\n/g, '\n'); + const parseAnsi = useCallback((inputStr: string, setId: () => number) => { + let cursorRow = 0; // current row + let cursorCol = 0; // current column + // screen content array + let screen = [['']]; + // replace carriage return and newline characters in the text + let input = inputStr.replace(/\r\n/g, '\n'); - lastIndex.current = 0; + lastIndex.current = 0; - // handle the \r and \n characters in the text - const handleText = (text: string) => { - let processed = ''; - for (let char of text) { - if (char === '\r') { - cursorCol = 0; // move to the beginning of the line - } else if (char === '\n') { - cursorRow++; // move to the next line - cursorCol = 0; // move to the beginning of the line - screen[cursorRow] = screen[cursorRow] || ['']; // create a new line if it does not exist - } else { - // add the character to the screen content array - screen[cursorRow][cursorCol] = char; - cursorCol++; - } - } - return processed; - }; - - let output = ''; // output text - - let match; - - // ANSI color map - const colorMap: Record = { - '30': 'black', - '31': 'red', - '32': 'green', - '33': 'yellow', - '34': 'blue', - '35': 'magenta', - '36': 'cyan', - '37': 'white' - }; - - let currentStyle = ''; // current text style - - // match ANSI control characters - while ((match = controlSeqRegex.exec(input)) !== null) { - // handle text before the control character - let textBeforeControl = input.slice(lastIndex.current, match.index); - output += handleText(textBeforeControl); // add the processed text to the output - lastIndex.current = controlSeqRegex.lastIndex; // update the last index - - const n = parseInt(match[1], 10) || 1; - const m = parseInt(match[2], 10) || 1; - const command = match[3]; - console.log('command', { - command, - cursorRow, - n - }); - - // handle ANSI control characters - switch (command) { - case 'A': // up - cursorRow = Math.max(0, cursorRow - n); - if (cursorRow === 0) { - // screen = [['']]; - // cursorCol = 0; - } - break; - case 'B': // down - cursorRow += n; - break; - case 'C': // right - cursorCol += n; - break; - case 'D': // left - cursorCol = Math.max(0, cursorCol - n); - break; - case 'H': // move the cursor to the specified position (n, m) - cursorRow = Math.max(0, n - 1); - cursorCol = Math.max(0, m - 1); - break; - case 'J': // clear the screen - if (n === 2) { - console.log('clear===='); - screen = [['']]; - cursorRow = 0; - cursorCol = 0; - } - break; - case 'm': // color - if (match[1] === '0') { - currentStyle = ''; - } else if (colorMap[match[1]]) { - currentStyle = `color: ${colorMap[match[1]]};`; - } - break; - } - - // check if the row and column are within the screen content array - while (screen.length <= cursorRow) { - screen.push(['']); - } - while (screen[cursorRow].length <= cursorCol) { - screen[cursorRow].push(''); + // handle the \r and \n characters in the text + const handleText = (text: string) => { + let processed = ''; + for (let char of text) { + if (char === '\r') { + cursorCol = 0; // move to the beginning of the line + } else if (char === '\n') { + cursorRow++; // move to the next line + cursorCol = 0; // move to the beginning of the line + screen[cursorRow] = screen[cursorRow] || ['']; // create a new line if it does not exist + } else { + // add the character to the screen content array + screen[cursorRow][cursorCol] = char; + cursorCol++; } } + return processed; + }; - // handle the remaining text - output += handleText(input.slice(lastIndex.current)); + let output = ''; // output text - let result = []; - for (let row = 0; row < screen.length; row++) { - let rowContent = screen[row].join(''); - result.push({ - content: removeBrackets(rowContent), - uid: setId() - }); - } - result.push({ - content: output, - uid: setId() + let match; + + // ANSI color map + const colorMap: Record = { + '30': 'black', + '31': 'red', + '32': 'green', + '33': 'yellow', + '34': 'blue', + '35': 'magenta', + '36': 'cyan', + '37': 'white' + }; + + let currentStyle = ''; // current text style + + // match ANSI control characters + while ((match = controlSeqRegex.exec(input)) !== null) { + // handle text before the control character + let textBeforeControl = input.slice(lastIndex.current, match.index); + output += handleText(textBeforeControl); // add the processed text to the output + lastIndex.current = controlSeqRegex.lastIndex; // update the last index + + const n = parseInt(match[1], 10) || 1; + const m = parseInt(match[2], 10) || 1; + const command = match[3]; + console.log('command', { + command, + cursorRow, + n }); - return result; - }, - [] - ); + // handle ANSI control characters + switch (command) { + case 'A': // up + cursorRow = Math.max(0, cursorRow - n); + if (cursorRow === 0) { + // screen = [['']]; + // cursorCol = 0; + } + break; + case 'B': // down + cursorRow += n; + break; + case 'C': // right + cursorCol += n; + break; + case 'D': // left + cursorCol = Math.max(0, cursorCol - n); + break; + case 'H': // move the cursor to the specified position (n, m) + cursorRow = Math.max(0, n - 1); + cursorCol = Math.max(0, m - 1); + break; + case 'J': // clear the screen + if (n === 2) { + console.log('clear===='); + screen = [['']]; + cursorRow = 0; + cursorCol = 0; + } + break; + case 'm': // color + if (match[1] === '0') { + currentStyle = ''; + } else if (colorMap[match[1]]) { + currentStyle = `color: ${colorMap[match[1]]};`; + } + break; + } + + // check if the row and column are within the screen content array + while (screen.length <= cursorRow) { + screen.push(['']); + } + while (screen[cursorRow].length <= cursorCol) { + screen[cursorRow].push(''); + } + } + + // handle the remaining text + output += handleText(input.slice(lastIndex.current)); + + let result = []; + for (let row = 0; row < screen.length; row++) { + let rowContent = screen[row].join(''); + result.push({ + content: removeBrackets(rowContent), + uid: setId() + }); + } + result.push({ + content: output, + uid: setId() + }); + + return result; + }, []); return { parseAnsi, isClean }; }; diff --git a/src/components/version-info/index.tsx b/src/components/version-info/index.tsx index a2d79199..9a104a7d 100644 --- a/src/components/version-info/index.tsx +++ b/src/components/version-info/index.tsx @@ -61,7 +61,12 @@ const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => { { version: currentVersion } )} -
diff --git a/src/hooks/use-overlay-scroller.ts b/src/hooks/use-overlay-scroller.ts index 4ac04138..2a71e7bc 100644 --- a/src/hooks/use-overlay-scroller.ts +++ b/src/hooks/use-overlay-scroller.ts @@ -56,7 +56,7 @@ export default function useOverlayScroller(options?: any) { }); instanceRef.current?.update?.(); }, 300), - [scrollEventElement, instanceRef] + [scrollEventElement.current, instanceRef.current] ); const scrollauto = React.useCallback(() => { @@ -65,7 +65,7 @@ export default function useOverlayScroller(options?: any) { behavior: 'auto' }); instanceRef.current?.update?.(); - }, [scrollEventElement, instanceRef]); + }, [scrollEventElement.current, instanceRef.current]); const throttledUpdateScrollerPosition = React.useCallback( (delay?: number) => { @@ -78,25 +78,25 @@ export default function useOverlayScroller(options?: any) { [throttledScroll, scrollauto] ); - // const createInstance = React.useCallback((el: any) => { - // if (el) { - // instanceRef.current?.destroy?.(); - // initialize(el); - // instanceRef.current = instance?.(); - // scrollEventElement.current = - // instanceRef.current?.elements()?.scrollEventElement; - // } - // }, []); - - React.useEffect(() => { - return () => { - instanceRef.current?.destroy?.(); - }; - }, []); + const createInstance = React.useCallback( + (el: any) => { + if (instanceRef.current) { + return; + } + if (el) { + initialize(el); + instanceRef.current = instance?.(); + scrollEventElement.current = + instanceRef.current?.elements()?.scrollEventElement; + } + }, + [initialize, instance] + ); return { - initialize, + initialize: createInstance, instance: instanceRef.current, + scrollEventElement: scrollEventElement.current, updateScrollerPosition: throttledUpdateScrollerPosition }; } diff --git a/src/layouts/rightRender.tsx b/src/layouts/rightRender.tsx index 977fa96c..7f396cd9 100644 --- a/src/layouts/rightRender.tsx +++ b/src/layouts/rightRender.tsx @@ -109,7 +109,7 @@ export const getRightRenderContent = (opts: { mode: 'vertical', expandIcon: false, // inlineCollapsed: collapsed, - triggerSubMenuAction: 'click', + triggerSubMenuAction: 'hover', items: [ { key: 'help', diff --git a/src/locales/en-US/resources.ts b/src/locales/en-US/resources.ts index 367bd283..14ad87f7 100644 --- a/src/locales/en-US/resources.ts +++ b/src/locales/en-US/resources.ts @@ -37,6 +37,7 @@ export default { 'resources.table.vramutilization': 'VRAM Utilization', 'resources.table.total': 'Total', 'resources.table.used': 'Used', + 'resources.table.allocated': 'Allocated', 'resources.table.wokers': 'workers', 'resources.worker.linuxormaxos': 'Linux or MacOS', 'resources.table.unified': 'Unified Memory', diff --git a/src/locales/en-US/users.ts b/src/locales/en-US/users.ts index daf35f6b..9879d5d4 100644 --- a/src/locales/en-US/users.ts +++ b/src/locales/en-US/users.ts @@ -27,5 +27,5 @@ export default { 'users.password.confirm.error': 'The two passwords entered do not match.', 'users.login.title': 'Log in to {name}', 'users.version.islatest': '{version} is the latest version', - 'users.version.update': 'Version {version} is available' + 'users.version.update': 'GPUStack {version} is available' }; diff --git a/src/locales/zh-CN/resources.ts b/src/locales/zh-CN/resources.ts index 6ccf4713..27895563 100644 --- a/src/locales/zh-CN/resources.ts +++ b/src/locales/zh-CN/resources.ts @@ -37,6 +37,7 @@ export default { 'resources.table.utilization': '利用率', 'resources.table.total': '总量', 'resources.table.used': '已用', + 'resources.table.allocated': '已分配', 'resources.table.wokers': 'workers', 'resources.table.unified': '统一内存', 'resources.worker.linuxormaxos': 'Linux 或 MacOS', diff --git a/src/locales/zh-CN/users.ts b/src/locales/zh-CN/users.ts index 03318a02..99d72382 100644 --- a/src/locales/zh-CN/users.ts +++ b/src/locales/zh-CN/users.ts @@ -26,5 +26,5 @@ export default { 'users.password.confirm.error': '两次输入的密码不一致', 'users.login.title': '登录 {name}', 'users.version.islatest': '{version} 已是最新版本', - 'users.version.update': '{version} 版本可供更新' + 'users.version.update': 'GPUStack {version} 版本可供更新' }; diff --git a/src/pages/llmodels/components/table-list.tsx b/src/pages/llmodels/components/table-list.tsx index 4adac3be..a2055037 100644 --- a/src/pages/llmodels/components/table-list.tsx +++ b/src/pages/llmodels/components/table-list.tsx @@ -39,11 +39,7 @@ import { queryModelInstancesList, updateModel } from '../apis'; -import { - InstanceStatusMap, - getSourceRepoConfigValue, - modelSourceMap -} from '../config'; +import { getSourceRepoConfigValue, modelSourceMap } from '../config'; import { FormData, ListItem, ModelInstanceListItem } from '../config/types'; import DeployModal from './deploy-modal'; import InstanceItem from './instance-item'; @@ -657,7 +653,6 @@ const Models: React.FC = ({ > diff --git a/src/pages/llmodels/components/view-logs-modal.tsx b/src/pages/llmodels/components/view-logs-modal.tsx index 6eab5201..9f42db79 100644 --- a/src/pages/llmodels/components/view-logs-modal.tsx +++ b/src/pages/llmodels/components/view-logs-modal.tsx @@ -66,7 +66,6 @@ const ViewCodeModal: React.FC = (props) => { = forwardRef((props, ref) => { ] : [...formatMessages], ...parameters, - stream: true + stream: true, + stream_otpions: { + include_usage: true + } }; const result: any = await fetchChunkedData({ data: chatParams, diff --git a/src/pages/playground/components/multiple-chat/model-item.tsx b/src/pages/playground/components/multiple-chat/model-item.tsx index 1ab04422..77f8e27d 100644 --- a/src/pages/playground/components/multiple-chat/model-item.tsx +++ b/src/pages/playground/components/multiple-chat/model-item.tsx @@ -173,7 +173,10 @@ const ModelItem: React.FC = forwardRef( ] : [...formatMessages], ...params, - stream: true + stream: true, + stream_otpions: { + include_usage: true + } }; // ============== payload end ================ const result: any = await fetchChunkedData({ diff --git a/src/pages/playground/components/view-code-modal.tsx b/src/pages/playground/components/view-code-modal.tsx index 19829b53..5427afc4 100644 --- a/src/pages/playground/components/view-code-modal.tsx +++ b/src/pages/playground/components/view-code-modal.tsx @@ -4,7 +4,7 @@ import { BulbOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Button, Modal } from 'antd'; import _ from 'lodash'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useState } from 'react'; type ViewModalProps = { systemMessage?: string; @@ -40,8 +40,6 @@ const ViewCodeModal: React.FC = (props) => { } = props || {}; const intl = useIntl(); - const editorRef = useRef(null); - const [loaded, setLoaded] = useState(false); const [codeValue, setCodeValue] = useState(''); const [lang, setLang] = useState(langMap.shell); diff --git a/src/pages/resources/components/gpus.tsx b/src/pages/resources/components/gpus.tsx index b1ec6a07..9cc93b39 100644 --- a/src/pages/resources/components/gpus.tsx +++ b/src/pages/resources/components/gpus.tsx @@ -1,3 +1,4 @@ +import AutoTooltip from '@/components/auto-tooltip'; import PageTools from '@/components/page-tools'; import ProgressBar from '@/components/progress-bar'; import useTableSort from '@/hooks/use-table-sort'; @@ -140,6 +141,16 @@ const GPUList: React.FC = () => { title={intl.formatMessage({ id: 'resources.table.workername' })} dataIndex="worker_name" key="worker_name" + width={200} + render={(text, record: GPUDeviceItem) => { + return ( + + + {text} + + + ); + }} /> { dataIndex="core" key="Core" render={(text, record: GPUDeviceItem) => { - return {record.core?.total}; + return <>{record.core ? {record.core?.total} : '-'}; }} /> */} { key="gpuUtil" render={(text, record: GPUDeviceItem) => { return ( - + <> + {record.core ? ( + + ) : ( + '-' + )} + ); }} /> @@ -192,7 +209,10 @@ const GPUList: React.FC = () => { {intl.formatMessage({ id: 'resources.table.used' })}:{' '} - {convertFileSize(record.memory?.used, 0)} + {convertFileSize( + record.memory?.used || record.memory?.allocated, + 0 + )} } diff --git a/src/pages/resources/components/workers.tsx b/src/pages/resources/components/workers.tsx index 765fe2e0..32e2be36 100644 --- a/src/pages/resources/components/workers.tsx +++ b/src/pages/resources/components/workers.tsx @@ -307,6 +307,14 @@ const Resources: React.FC = () => { title={intl.formatMessage({ id: 'common.table.name' })} dataIndex="name" key="name" + width={100} + render={(text, record: ListItem) => { + return ( + + {record.name} + + ); + }} /> { > [{item.index}] - + {item.core ? ( + + ) : ( + '-' + )} ); } @@ -455,7 +467,11 @@ const Resources: React.FC = () => { {intl.formatMessage({ id: 'resources.table.used' })} - : {convertFileSize(item.memory?.used, 0)} + :{' '} + {convertFileSize( + item.memory?.used || item.memory?.allocated, + 0 + )} }