chore: stt player ui
This commit is contained in:
Generated
+4
-28
@@ -4894,7 +4894,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.24.5
|
'@babel/core': 7.24.5
|
||||||
postcss: 7.0.39
|
postcss: 7.0.39
|
||||||
postcss-syntax: 0.36.2(postcss@7.0.39)
|
postcss-syntax: 0.36.2(postcss@8.4.38)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
dev: false
|
dev: false
|
||||||
@@ -4935,7 +4935,7 @@ packages:
|
|||||||
postcss-syntax: '>=0.36.2'
|
postcss-syntax: '>=0.36.2'
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss: 7.0.39
|
postcss: 7.0.39
|
||||||
postcss-syntax: 0.36.2(postcss@7.0.39)
|
postcss-syntax: 0.36.2(postcss@8.4.38)
|
||||||
remark: 13.0.0
|
remark: 13.0.0
|
||||||
unist-util-find-all-after: 3.0.2
|
unist-util-find-all-after: 3.0.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -14301,7 +14301,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
htmlparser2: 3.10.1
|
htmlparser2: 3.10.1
|
||||||
postcss: 7.0.39
|
postcss: 7.0.39
|
||||||
postcss-syntax: 0.36.2(postcss@7.0.39)
|
postcss-syntax: 0.36.2(postcss@8.4.38)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/postcss-image-set-function@4.0.7(postcss@8.4.38):
|
/postcss-image-set-function@4.0.7(postcss@8.4.38):
|
||||||
@@ -14775,30 +14775,6 @@ packages:
|
|||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
postcss: 8.4.38
|
postcss: 8.4.38
|
||||||
|
|
||||||
/postcss-syntax@0.36.2(postcss@7.0.39):
|
|
||||||
resolution: {integrity: sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==}
|
|
||||||
peerDependencies:
|
|
||||||
postcss: '>=5.0.0'
|
|
||||||
postcss-html: '*'
|
|
||||||
postcss-jsx: '*'
|
|
||||||
postcss-less: '*'
|
|
||||||
postcss-markdown: '*'
|
|
||||||
postcss-scss: '*'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
postcss-html:
|
|
||||||
optional: true
|
|
||||||
postcss-jsx:
|
|
||||||
optional: true
|
|
||||||
postcss-less:
|
|
||||||
optional: true
|
|
||||||
postcss-markdown:
|
|
||||||
optional: true
|
|
||||||
postcss-scss:
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
postcss: 7.0.39
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/postcss-syntax@0.36.2(postcss@8.4.38):
|
/postcss-syntax@0.36.2(postcss@8.4.38):
|
||||||
resolution: {integrity: sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==}
|
resolution: {integrity: sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -18099,7 +18075,7 @@ packages:
|
|||||||
postcss-sass: 0.4.4
|
postcss-sass: 0.4.4
|
||||||
postcss-scss: 2.1.1
|
postcss-scss: 2.1.1
|
||||||
postcss-selector-parser: 6.0.16
|
postcss-selector-parser: 6.0.16
|
||||||
postcss-syntax: 0.36.2(postcss@7.0.39)
|
postcss-syntax: 0.36.2(postcss@8.4.38)
|
||||||
postcss-value-parser: 4.2.0
|
postcss-value-parser: 4.2.0
|
||||||
resolve-from: 5.0.0
|
resolve-from: 5.0.0
|
||||||
slash: 3.0.0
|
slash: 3.0.0
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.player-wrap {
|
.player-wrap {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: var(--ant-color-fill-quaternary);
|
// background-color: var(--ant-color-fill-quaternary);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|
||||||
.player-ui {
|
.player-ui {
|
||||||
@@ -13,10 +13,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-inline: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.play-btn {
|
.play-btn {
|
||||||
@@ -43,7 +43,6 @@
|
|||||||
|
|
||||||
.slider-inner {
|
.slider-inner {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
margin-inline: 10px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,8 +54,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.time {
|
.time {
|
||||||
|
width: 52px;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
&.current {
|
&.current {
|
||||||
margin-left: 6px;
|
text-align: left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +67,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ interface AudioPlayerProps {
|
|||||||
height?: number;
|
height?: number;
|
||||||
width?: number;
|
width?: number;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
|
extra?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const speedOptions = [
|
const speedOptions = [
|
||||||
@@ -42,7 +43,7 @@ const speedConfig = {
|
|||||||
|
|
||||||
const AudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
const AudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { autoplay = false, speed: defaultSpeed = 1 } = props;
|
const { autoplay = false, speed: defaultSpeed = 1, extra } = props;
|
||||||
const audioRef = React.useRef<HTMLAudioElement>(null);
|
const audioRef = React.useRef<HTMLAudioElement>(null);
|
||||||
const [audioState, setAudioState] = React.useState<{
|
const [audioState, setAudioState] = React.useState<{
|
||||||
currentTime: number;
|
currentTime: number;
|
||||||
@@ -206,76 +207,89 @@ const AudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
|||||||
<div className="progress-bar">
|
<div className="progress-bar">
|
||||||
<span className="file-name">{props.name}</span>
|
<span className="file-name">{props.name}</span>
|
||||||
<div className="slider">
|
<div className="slider">
|
||||||
<span className="time current">
|
{/* <span className="time current">
|
||||||
{' '}
|
{' '}
|
||||||
{formatTime(audioState.currentTime)}
|
{formatTime(audioState.currentTime)}
|
||||||
</span>
|
</span> */}
|
||||||
<div className="slider-inner">
|
<div className="slider-inner">
|
||||||
<Slider
|
<Slider
|
||||||
tooltip={{ open: false }}
|
tooltip={{ open: false }}
|
||||||
min={0}
|
min={0}
|
||||||
step={1}
|
step={1}
|
||||||
|
styles={{
|
||||||
|
rail: {
|
||||||
|
// height: 6
|
||||||
|
}
|
||||||
|
}}
|
||||||
max={audioState.duration}
|
max={audioState.duration}
|
||||||
value={audioState.currentTime}
|
value={audioState.currentTime}
|
||||||
onChange={handleCurrentChange}
|
onChange={handleCurrentChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="time">{formatTime(audioState.duration)}</span>
|
{/* <span className="time">{formatTime(audioState.duration)}</span> */}
|
||||||
</div>
|
</div>
|
||||||
<div className="controls">
|
<div className="controls">
|
||||||
<Tooltip
|
<div className="audio-control flex-center">
|
||||||
title={intl.formatMessage({
|
<span className="time current">
|
||||||
id: 'playground.audio.button.slow'
|
{' '}
|
||||||
})}
|
{formatTime(audioState.currentTime)}
|
||||||
>
|
</span>
|
||||||
<Button
|
<Tooltip
|
||||||
type="text"
|
title={intl.formatMessage({
|
||||||
size="small"
|
id: 'playground.audio.button.slow'
|
||||||
className="backward"
|
})}
|
||||||
disabled={
|
|
||||||
speed === speedConfig.min || speed < speedConfig.min
|
|
||||||
}
|
|
||||||
onClick={handleReduceSpeed}
|
|
||||||
>
|
>
|
||||||
<FastBackwardOutlined className="font-size-20" />
|
<Button
|
||||||
</Button>
|
type="text"
|
||||||
</Tooltip>
|
size="small"
|
||||||
<span className="play-btn">
|
className="backward"
|
||||||
<Button
|
disabled={
|
||||||
size="middle"
|
speed === speedConfig.min || speed < speedConfig.min
|
||||||
type="text"
|
}
|
||||||
onClick={handlePlay}
|
onClick={handleReduceSpeed}
|
||||||
disabled={!audioState?.duration}
|
>
|
||||||
icon={
|
<FastBackwardOutlined className="font-size-20" />
|
||||||
!playOn ? (
|
</Button>
|
||||||
<PlayCircleFilled
|
</Tooltip>
|
||||||
style={{ fontSize: '22px' }}
|
<span className="play-btn">
|
||||||
></PlayCircleFilled>
|
<Button
|
||||||
) : (
|
size="middle"
|
||||||
<PauseCircleFilled
|
type="text"
|
||||||
style={{ fontSize: '22px' }}
|
onClick={handlePlay}
|
||||||
></PauseCircleFilled>
|
disabled={!audioState?.duration}
|
||||||
)
|
icon={
|
||||||
}
|
!playOn ? (
|
||||||
></Button>
|
<PlayCircleFilled
|
||||||
</span>
|
style={{ fontSize: '22px' }}
|
||||||
<Tooltip
|
></PlayCircleFilled>
|
||||||
title={intl.formatMessage({
|
) : (
|
||||||
id: 'playground.audio.button.fast'
|
<PauseCircleFilled
|
||||||
})}
|
style={{ fontSize: '22px' }}
|
||||||
>
|
></PauseCircleFilled>
|
||||||
<Button
|
)
|
||||||
type="text"
|
}
|
||||||
size="small"
|
></Button>
|
||||||
className="forward"
|
</span>
|
||||||
disabled={
|
<Tooltip
|
||||||
speed === speedConfig.max || speed > speedConfig.max
|
title={intl.formatMessage({
|
||||||
}
|
id: 'playground.audio.button.fast'
|
||||||
onClick={handleAddSpeed}
|
})}
|
||||||
>
|
>
|
||||||
<FastForwardOutlined className="font-size-20" />
|
<Button
|
||||||
</Button>
|
type="text"
|
||||||
</Tooltip>
|
size="small"
|
||||||
|
className="forward"
|
||||||
|
disabled={
|
||||||
|
speed === speedConfig.max || speed > speedConfig.max
|
||||||
|
}
|
||||||
|
onClick={handleAddSpeed}
|
||||||
|
>
|
||||||
|
<FastForwardOutlined className="font-size-20" />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<span className="time">{formatTime(audioState.duration)}</span>
|
||||||
|
</div>
|
||||||
|
{extra}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -139,7 +139,11 @@ const LogsList: React.FC<LogsListProps> = forwardRef((props, ref) => {
|
|||||||
<div className={classNames('content')}>
|
<div className={classNames('content')}>
|
||||||
{_.map(dataList, (item: any, index: number) => {
|
{_.map(dataList, (item: any, index: number) => {
|
||||||
return (
|
return (
|
||||||
<div key={item.uid} className={classNames('text')}>
|
<div
|
||||||
|
key={item.uid}
|
||||||
|
className={classNames('text')}
|
||||||
|
data-uid={item.uid}
|
||||||
|
>
|
||||||
{item.content}
|
{item.content}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ const removeBrackets = (str: string) => {
|
|||||||
return str?.replace?.(/^\(…\)/, '');
|
return str?.replace?.(/^\(…\)/, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeBracketsFromLine = (row: string) => {
|
||||||
|
return row.startsWith('(…)') ? row.slice(3) : row;
|
||||||
|
};
|
||||||
|
|
||||||
class AnsiParser {
|
class AnsiParser {
|
||||||
private cursorRow: number = 0;
|
private cursorRow: number = 0;
|
||||||
private cursorCol: number = 0;
|
private cursorCol: number = 0;
|
||||||
@@ -12,6 +16,8 @@ class AnsiParser {
|
|||||||
private uid: number = 0;
|
private uid: number = 0;
|
||||||
private isProcessing: boolean = false;
|
private isProcessing: boolean = false;
|
||||||
private taskQueue: string[] = [];
|
private taskQueue: string[] = [];
|
||||||
|
private page: number = 1;
|
||||||
|
private pageSize: number = 500;
|
||||||
private colorMap = {
|
private colorMap = {
|
||||||
'30': 'black',
|
'30': 'black',
|
||||||
'31': 'red',
|
'31': 'red',
|
||||||
@@ -33,6 +39,11 @@ class AnsiParser {
|
|||||||
this.screen = [['']];
|
this.screen = [['']];
|
||||||
this.rawDataRows = 0;
|
this.rawDataRows = 0;
|
||||||
this.uid = this.uid + 1;
|
this.uid = this.uid + 1;
|
||||||
|
this.page = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setPage(page: number) {
|
||||||
|
this.page = page;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setId() {
|
private setId() {
|
||||||
@@ -125,9 +136,9 @@ class AnsiParser {
|
|||||||
const remainingText = input.slice(lastIndex);
|
const remainingText = input.slice(lastIndex);
|
||||||
this.handleText(remainingText);
|
this.handleText(remainingText);
|
||||||
|
|
||||||
const result = this.screen.map((row) => ({
|
const result = this.screen.map((row, index) => ({
|
||||||
content: removeBrackets(row.join('')),
|
content: removeBracketsFromLine(row.join('')),
|
||||||
uid: this.setId()
|
uid: `${this.page}-${index}`
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -176,7 +187,8 @@ class AnsiParser {
|
|||||||
const parser = new AnsiParser();
|
const parser = new AnsiParser();
|
||||||
|
|
||||||
self.onmessage = function (event) {
|
self.onmessage = function (event) {
|
||||||
const { inputStr, reset } = event.data;
|
const { inputStr, reset, page } = event.data;
|
||||||
|
parser.setPage(page);
|
||||||
if (reset) {
|
if (reset) {
|
||||||
parser.reset();
|
parser.reset();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
}
|
}
|
||||||
logParseWorker.current.postMessage({
|
logParseWorker.current.postMessage({
|
||||||
inputStr: data,
|
inputStr: data,
|
||||||
|
page: pageRef.current,
|
||||||
reset: clearScreen.current
|
reset: clearScreen.current
|
||||||
});
|
});
|
||||||
clearScreen.current = false;
|
clearScreen.current = false;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import useSetChunkRequest from '@/hooks/use-chunk-request';
|
import useSetChunkRequest from '@/hooks/use-chunk-request';
|
||||||
import { FitAddon } from '@xterm/addon-fit';
|
import { FitAddon } from '@xterm/addon-fit';
|
||||||
import { WebglAddon } from '@xterm/addon-webgl';
|
|
||||||
import { Terminal } from '@xterm/xterm';
|
import { Terminal } from '@xterm/xterm';
|
||||||
import '@xterm/xterm/css/xterm.css';
|
import '@xterm/xterm/css/xterm.css';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@@ -126,7 +125,6 @@ const LogsViewer: React.FC<LogsViewerProps> = forwardRef((props, ref) => {
|
|||||||
});
|
});
|
||||||
fitAddonRef.current = new FitAddon();
|
fitAddonRef.current = new FitAddon();
|
||||||
termRef.current.loadAddon(fitAddonRef.current);
|
termRef.current.loadAddon(fitAddonRef.current);
|
||||||
termRef.current.loadAddon(new WebglAddon());
|
|
||||||
termRef.current.open(termwrapRef.current);
|
termRef.current.open(termwrapRef.current);
|
||||||
|
|
||||||
// add event
|
// add event
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
h3,
|
h3,
|
||||||
@@ -80,6 +84,10 @@
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
img[src^="https://img.shields.io/"], img[src*="badge.svg"]
|
||||||
|
{
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
video {
|
video {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|||||||
@@ -45,10 +45,6 @@ const MarkdownViewer: React.FC<MarkdownViewerProps> = ({
|
|||||||
// 'bibtex'
|
// 'bibtex'
|
||||||
];
|
];
|
||||||
|
|
||||||
// renderer.link = ({ href, title, text }) => {
|
|
||||||
// return `<a href="${href}" title="${title || ''}" target="_blank" rel="noopener noreferrer">${text}</a>`;
|
|
||||||
// };
|
|
||||||
|
|
||||||
const isValidURL = useCallback((url: string) => {
|
const isValidURL = useCallback((url: string) => {
|
||||||
const pattern = /^(https?:\/\/|\/\/)([^\s/$.?#].[^\s]*)$/;
|
const pattern = /^(https?:\/\/|\/\/)([^\s/$.?#].[^\s]*)$/;
|
||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export default {};
|
|
||||||
@@ -307,7 +307,7 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
{!isRecording && (
|
{!isRecording && (
|
||||||
<UploadAudio
|
<UploadAudio
|
||||||
type="default"
|
type="default"
|
||||||
accept={SpeechToTextFormat.join(',')}
|
accept={SpeechToTextFormat.join(', ')}
|
||||||
onChange={handleUploadChange}
|
onChange={handleUploadChange}
|
||||||
></UploadAudio>
|
></UploadAudio>
|
||||||
)}
|
)}
|
||||||
@@ -322,18 +322,40 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{audioData ? (
|
{audioData ? (
|
||||||
<div
|
<div className="flex-between flex-center justify-center relative">
|
||||||
className="flex-between flex-center justify-center relative"
|
|
||||||
style={{ paddingInline: 80 }}
|
|
||||||
>
|
|
||||||
<div style={{ width: 600 }}>
|
<div style={{ width: 600 }}>
|
||||||
<AudioPlayer
|
<AudioPlayer
|
||||||
url={audioData.url}
|
url={audioData.url}
|
||||||
name={audioData.name}
|
name={audioData.name}
|
||||||
duration={audioData.duration}
|
duration={audioData.duration}
|
||||||
|
extra={
|
||||||
|
<Tooltip
|
||||||
|
title={
|
||||||
|
loading
|
||||||
|
? intl.formatMessage({
|
||||||
|
id: 'playground.audio.generating'
|
||||||
|
})
|
||||||
|
: intl.formatMessage({
|
||||||
|
id: 'playground.audio.button.generate'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
<Button
|
||||||
|
disabled={!audioData}
|
||||||
|
loading={loading}
|
||||||
|
type="primary"
|
||||||
|
size="middle"
|
||||||
|
shape="circle"
|
||||||
|
onClick={handleOnGenerate}
|
||||||
|
icon={<SendOutlined></SendOutlined>}
|
||||||
|
></Button>
|
||||||
|
}
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
></AudioPlayer>
|
></AudioPlayer>
|
||||||
</div>
|
</div>
|
||||||
<div
|
{/* <div
|
||||||
style={{
|
style={{
|
||||||
padding: '16px',
|
padding: '16px',
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
@@ -365,7 +387,7 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
></Button>
|
></Button>
|
||||||
}
|
}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
renderAniamtion()
|
renderAniamtion()
|
||||||
@@ -385,15 +407,14 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
className="message-list-wrap"
|
className="message-list-wrap"
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
position: 'relative',
|
position: 'relative'
|
||||||
borderTop: '1px solid var(--ant-color-split)'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{messageList?.length > 0 && (
|
{messageList?.length > 0 && (
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 0,
|
top: 20,
|
||||||
right: 2
|
right: 2
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user