chore: chat hooks

This commit is contained in:
jialin
2025-02-19 20:50:32 +08:00
parent 9e0634f449
commit f1ab927a7e
16 changed files with 827 additions and 756 deletions
+58
View File
@@ -0,0 +1,58 @@
.alert-info-block {
padding: 6px 10px;
position: relative;
padding-top: 35px;
text-align: center;
border-radius: var(--border-radius-base);
margin: 0;
border: 1px solid transparent;
.ant-typography {
margin-bottom: 0;
}
&.danger {
border-color: var(--ant-color-error-border);
background-color: var(--ant-color-error-bg);
}
&.warning {
border-color: var(--ant-color-warning-border);
background-color: var(--ant-color-warning-bg);
}
.title {
position: absolute;
left: 0;
top: 0;
display: flex;
height: 30px;
width: 100%;
padding: 5px 10px;
border-radius: var(--border-radius-base) var(--border-radius-base) 0 0;
&.danger {
background-color: var(--ant-color-error-bg-hover);
}
&.warning {
background-color: var(--ant-color-warning-bg-hover);
}
.info-icon {
margin-right: 8px;
&.danger {
color: var(--ant-color-error);
}
&.warning {
color: var(--ant-color-warning);
}
}
.text {
font-weight: var(--font-weight-bold);
}
}
}
+48
View File
@@ -0,0 +1,48 @@
import { WarningFilled } from '@ant-design/icons';
import { Typography } from 'antd';
import classNames from 'classnames';
import React from 'react';
import './block.less';
interface AlertInfoProps {
type: 'danger' | 'warning';
message: string;
rows?: number;
icon?: React.ReactNode;
ellipsis?: boolean;
style?: React.CSSProperties;
title: React.ReactNode;
}
const AlertInfo: React.FC<AlertInfoProps> = (props) => {
const { message, type, rows = 1, ellipsis, style, title } = props;
return (
<>
{message ? (
<div
className={classNames('alert-info-block', type)}
style={{ ...style }}
>
<Typography.Paragraph
ellipsis={
ellipsis !== undefined
? ellipsis
: {
rows: rows,
tooltip: message
}
}
>
<div className={classNames('title', type)}>
<WarningFilled className={classNames('info-icon', type)} />
<span className="text">{title}</span>
</div>
{message}
</Typography.Paragraph>
</div>
) : null}
</>
);
};
export default React.memo(AlertInfo);
-147
View File
@@ -1,147 +0,0 @@
import useSetChunkFetch from '@/hooks/use-chunk-fetch';
import useOverlayScroller from '@/hooks/use-overlay-scroller';
import '@xterm/xterm/css/xterm.css';
import classNames from 'classnames';
import _ from 'lodash';
import { memo, useCallback, useEffect, useRef, useState } from 'react';
import { replaceLineRegex } from './config';
import useParseAnsi from './parse-ansi';
import './styles/index.less';
interface LogsViewerProps {
height: number;
content?: string;
url: string;
params?: object;
}
const LogsViewer: React.FC<LogsViewerProps> = (props) => {
const { height, url } = props;
const {
initialize,
updateScrollerPosition,
generateInstance,
scrollEventElement,
instance,
initialized
} = useOverlayScroller({
options: {
theme: 'os-theme-light'
}
});
const { isClean, parseAnsi } = useParseAnsi();
const { setChunkFetch } = useSetChunkFetch();
const chunkRequedtRef = useRef<any>(null);
const scroller = useRef<any>({});
const cacheDataRef = useRef<any>('');
const uidRef = useRef<any>(0);
const [logs, setLogs] = useState<any[]>([]);
const stopScroll = useRef(false);
const setId = () => {
uidRef.current += 1;
return uidRef.current;
};
const updateContent = useCallback(
(inputStr: string) => {
const data = inputStr.replace(replaceLineRegex, '\n');
if (isClean(data)) {
cacheDataRef.current = data;
} else {
cacheDataRef.current += data;
}
const res = parseAnsi(cacheDataRef.current, setId);
console.log('res===', res);
setLogs(res);
},
[setLogs, setId]
);
const createChunkConnection = async () => {
chunkRequedtRef.current?.current?.abort?.();
chunkRequedtRef.current = setChunkFetch({
url,
params: {
...props.params,
watch: true
},
contentType: 'text',
handler: updateContent
});
};
const debounceResetStopScroll = _.debounce(() => {
stopScroll.current = false;
}, 30000);
const handleOnWheel = useCallback(
(e: any) => {
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, scrollEventElement]
);
const debounceUpdateScrollerPosition = _.debounce(() => {
generateInstance();
updateScrollerPosition(0);
}, 200);
useEffect(() => {
createChunkConnection();
return () => {
chunkRequedtRef.current?.current?.abort?.();
};
}, [url, props.params]);
useEffect(() => {
if (scroller.current) {
initialize(scroller.current);
}
}, [scroller.current, initialize]);
useEffect(() => {
if (logs.length && !stopScroll.current && instance) {
updateScrollerPosition(0);
} else if (logs.length && !stopScroll.current && scroller.current) {
if (!initialized) {
initialize(scroller.current);
}
if (!instance) {
debounceUpdateScrollerPosition();
} else {
updateScrollerPosition(0);
}
}
}, [logs, stopScroll.current, instance, scroller.current]);
return (
<div className="logs-viewer-wrap-w2">
<div
className="wrap"
style={{ height: height }}
ref={scroller}
onWheel={handleOnWheel}
>
<div className={classNames('content')}>
{_.map(logs, (item: any, index: number) => {
return (
<div key={item.uid} className="text">
{item.content}
</div>
);
})}
</div>
</div>
</div>
);
};
export default memo(LogsViewer);
@@ -1,149 +0,0 @@
import _, { throttle } from 'lodash';
import List from 'rc-virtual-list';
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState
} from 'react';
import './styles/logs-list.less';
interface LogsInnerProps {
ref?: any;
data: { content: string; uid: number }[];
onScroll?: (data: { isTop: boolean; isBottom: boolean }) => void;
diffHeight?: number;
}
const LogsInner: React.FC<LogsInnerProps> = forwardRef((props, ref) => {
const { data, diffHeight = 96, onScroll } = props;
const viewportHeight = window.innerHeight;
const viewHeight = viewportHeight - diffHeight;
const [innerHieght, setInnerHeight] = useState(viewHeight);
const scroller = useRef<any>(null);
const stopScroll = useRef(false);
const logsWrapper = useRef<any>(null);
const RC_VIRTUAL_LIST_HOLDER_CLASS = '.rc-virtual-list-holder';
const updataPositionToBottom = useCallback(
throttle(() => {
if (!stopScroll.current && data.length > 0) {
scroller.current?.scrollTo?.({
index: data.length - 1,
align: 'bottom'
});
}
}, 200),
[data, scroller.current, stopScroll.current]
);
const updataPositionToTop = useCallback(
throttle(() => {
if (!stopScroll.current && data.length > 0) {
scroller.current?.scrollTo?.({
index: 0,
align: 'bottom'
});
}
}, 200),
[data, scroller.current, stopScroll.current]
);
const updatePositionToTop = useCallback(
_.throttle((data: { isTop: boolean; isBottom: boolean }) => {
props.onScroll?.(data);
}, 200),
[props.onScroll]
);
const isScrollBottom = useCallback((root: HTMLElement) => {
const virtualList = root.querySelector(RC_VIRTUAL_LIST_HOLDER_CLASS);
if (!virtualList) {
return false;
}
return (
virtualList.scrollTop >=
virtualList.scrollHeight - virtualList.clientHeight
);
}, []);
const isScrollTop = useCallback((root: HTMLElement) => {
const virtualList = root.querySelector(RC_VIRTUAL_LIST_HOLDER_CLASS);
if (!virtualList) {
return false;
}
return virtualList.scrollTop <= 0;
}, []);
const handleOnScroll = useCallback((e: any) => {
const isBottom = isScrollBottom(logsWrapper.current);
const isTop = isScrollTop(logsWrapper.current);
if (isBottom) {
stopScroll.current = false;
} else {
stopScroll.current = true;
}
updatePositionToTop({
isTop,
isBottom
});
}, []);
useImperativeHandle(ref, () => ({
scrollToBottom() {
updataPositionToBottom();
},
scrollToTop() {
updataPositionToTop();
}
}));
useEffect(() => {
updataPositionToBottom();
}, [updataPositionToBottom]);
useEffect(() => {
const handleResize = throttle(() => {
const viewportHeight = window.innerHeight;
const viewHeight = viewportHeight - diffHeight;
setInnerHeight(viewHeight);
}, 100);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, [diffHeight]);
return (
<div ref={logsWrapper} className="logs-wrap" style={{ height: '100%' }}>
<List
ref={scroller}
onScroll={handleOnScroll}
data={data}
itemHeight={22}
height={innerHieght}
itemKey="uid"
className="content"
styles={{
verticalScrollBar: {
width: 'var(--scrollbar-size)'
},
verticalScrollBarThumb: {
borderRadius: '4px',
backgroundColor: 'var(--scrollbar-handle-light-bg)'
}
}}
>
{(item: any, index: number) => (
<div key={item.uid} className="text">
{item.content}
</div>
)}
</List>
</div>
);
});
export default React.memo(LogsInner);
-1
View File
@@ -28,7 +28,6 @@ export interface TableHeaderProps {
sortOrder?: 'ascend' | 'descend' | null;
dataIndex: string;
onSort?: (dataIndex: string, order: 'ascend' | 'descend') => void;
children?: React.ReactElement<SealColumnProps>;
title: React.ReactNode;
style?: React.CSSProperties;
firstCell?: boolean;