import useSetChunkRequest from '@/hooks/use-chunk-request'; import { formatOrdinal } from '@/utils'; import { CloseOutlined, QuestionCircleOutlined } from '@ant-design/icons'; import { BaseSelect, LogsViewer } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Modal, Tooltip } from 'antd'; import dayjs from 'dayjs'; import React, { useCallback, useEffect, useState } from 'react'; import { MODELS_API } from '../apis'; import { InstanceRealtimeLogStatus, InstanceStatusMap } from '../config'; import useQueryModelInstanceRestartCount from '../services/use-query-instance-restart-count'; type ViewModalProps = { open: boolean; url: string; id?: number; modelId?: number | string; tail?: number; status?: string; onCancel: () => void; }; const ViewLogsModal: React.FC = (props) => { const intl = useIntl(); const { setChunkRequest } = useSetChunkRequest(); const { open, url, onCancel, tail, status } = props || {}; const [enableScorllLoad, setEnableScorllLoad] = useState(true); const [isDownloading, setIsDownloading] = useState( status === InstanceStatusMap.Downloading ); const [record, setRecord] = useState(0); const [params, setParams] = useState({ follow: true }); const logsViewerRef = React.useRef(null); const { countOptions, fetchData, cancelRequest } = useQueryModelInstanceRestartCount(); const requestRef = React.useRef(null); const contentRef = React.useRef(null); const handleCancel = useCallback(() => { logsViewerRef.current?.abort(); onCancel(); }, [onCancel]); const updateHandler = (list: any) => { const data = list?.find((item: any) => item.data?.id === props.id); // state in InstanceRealtimeLogStatus will not enable scorll load, because it is in the trasisition state if (data) { setIsDownloading(data?.data?.state === InstanceStatusMap.Downloading); setEnableScorllLoad( () => !InstanceRealtimeLogStatus.includes(data?.data?.state) ); } }; useEffect(() => { const handleKeyDown = (e: any) => { if ((e.ctrlKey || e.metaKey) && e.key === 'a') { e.preventDefault(); if (contentRef.current) { const range = document.createRange(); range.selectNodeContents(contentRef.current); const selection = window.getSelection(); selection?.removeAllRanges(); selection?.addRange(range); } } }; if (open) { document.addEventListener('keydown', handleKeyDown); } return () => { document.removeEventListener('keydown', handleKeyDown); }; }, [open]); const cancelOnClose = () => { logsViewerRef.current?.abort(); requestRef.current?.current?.cancel?.(); cancelRequest(); }; const handleOnChange = (value: number, option: any) => { if (!option) { setParams({ follow: true }); } else { setParams({ follow: true, watch: false, restart_count: option.value, worker_id: option.worker_id }); } setRecord(value); }; const renderLabel = (label: string, time: string) => { return ( {label} {dayjs(time).format('YYYY-MM-DD HH:mm:ss')} ); }; const optionRender = (option: any) => { const { data = {} } = option || {}; let label = formatOrdinal(data.value); if (data.isCurrent) { label = intl.formatMessage({ id: 'models.instance.currentRun' }); } if (data.isPrevious) { label = intl.formatMessage({ id: 'models.instance.previousRun' }); } return renderLabel(label, data.start_at); }; const labelRender = (option: any) => { const data = countOptions.find((item) => item.value === option.value); if (!data) return ''; return optionRender({ data }); }; const renderTitle = () => { return ( {intl.formatMessage({ id: 'common.button.viewlog' })} {countOptions.length > 1 && ( {intl.formatMessage({ id: 'models.instance.startHistory' })} {intl.formatMessage({ id: 'models.instance.previousRun' })} : {intl.formatMessage({ id: 'models.instance.startHistory.tips' })} } > } options={countOptions} labelRender={labelRender} optionRender={optionRender} style={{ width: 400, marginRight: 16 }} > )} ); }; useEffect(() => { if (open && props.id) { requestRef.current?.current?.cancel?.(); requestRef.current = setChunkRequest({ url: `${MODELS_API}/${props.modelId}/instances`, handler: updateHandler }); fetchData(props.id).then((list) => { const lastItem = list?.[0]; if (lastItem) { setRecord(lastItem.value); } }); } else { cancelOnClose(); } return () => { cancelOnClose(); }; }, [props.id, open]); return (
); }; export default ViewLogsModal;