fix(style): playground image incomplete
This commit is contained in:
@@ -49,5 +49,11 @@
|
||||
|
||||
.speaker {
|
||||
margin-left: 10px;
|
||||
position: relative;
|
||||
|
||||
.volume-slider {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { formatTime } from '@/utils/index';
|
||||
import { PauseCircleFilled, PlayCircleFilled } from '@ant-design/icons';
|
||||
import { Button, Slider } from 'antd';
|
||||
import { Button, Slider, Tooltip } from 'antd';
|
||||
import { round } from 'lodash';
|
||||
import React, {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle
|
||||
} from 'react';
|
||||
import CheckButtons from '../check-buttons';
|
||||
import IconFont from '../icon-font';
|
||||
import './index.less';
|
||||
|
||||
@@ -21,6 +23,13 @@ interface AudioPlayerProps {
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
const speedOptions = [
|
||||
{ label: '1x', value: 1 },
|
||||
{ label: '2x', value: 2 },
|
||||
{ label: '3x', value: 3 },
|
||||
{ label: '4x', value: 4 }
|
||||
];
|
||||
|
||||
const AudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
||||
const { autoplay = false, speed: defaultSpeed = 1 } = props;
|
||||
const audioRef = React.useRef<HTMLAudioElement>(null);
|
||||
@@ -45,6 +54,14 @@ const AudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
||||
audioRef.current?.pause();
|
||||
}
|
||||
}));
|
||||
const handleShowVolume = useCallback(() => {
|
||||
setSpeakerOn(!speakerOn);
|
||||
}, [speakerOn]);
|
||||
|
||||
const handleSeepdChange = useCallback((value: number | string) => {
|
||||
setSpeed(value as number);
|
||||
audioRef.current!.playbackRate = value as number;
|
||||
}, []);
|
||||
|
||||
const handleAudioOnPlay = useCallback(() => {
|
||||
timer.current = setInterval(() => {
|
||||
@@ -78,16 +95,22 @@ const AudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
||||
setPlayOn(!playOn);
|
||||
}, [playOn]);
|
||||
|
||||
const handleFormatVolume = (val: number) => {
|
||||
return `${round(val * 100)}%`;
|
||||
};
|
||||
|
||||
const handleVolumeChange = useCallback((value: number) => {
|
||||
audioRef.current!.volume = round(value, 2);
|
||||
setVolume(round(value, 2));
|
||||
}, []);
|
||||
|
||||
const initPlayerConfig = useCallback(() => {
|
||||
// set volume
|
||||
audioRef.current!.volume = volume;
|
||||
// set playback rate
|
||||
audioRef.current!.playbackRate = speed;
|
||||
}, []);
|
||||
|
||||
const handleLoadedMetadata = useCallback(
|
||||
(data: any) => {
|
||||
console.log('loadmetadata++++++++');
|
||||
const duration = Math.ceil(audioRef.current?.duration || 0);
|
||||
setAudioState({
|
||||
currentTime: 0,
|
||||
@@ -158,7 +181,23 @@ const AudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
||||
onChange={handleCurrentChange}
|
||||
/>
|
||||
</div>
|
||||
<span>{props.speed ? `${props.speed}x` : '1x'}</span>
|
||||
<Tooltip
|
||||
overlayInnerStyle={{
|
||||
backgroundColor: 'var(--color-white-1)'
|
||||
}}
|
||||
arrow={false}
|
||||
title={
|
||||
<CheckButtons
|
||||
options={speedOptions}
|
||||
onChange={handleSeepdChange}
|
||||
size="small"
|
||||
></CheckButtons>
|
||||
}
|
||||
>
|
||||
<span style={{ cursor: 'pointer' }}>
|
||||
{speed ? `${speed}x` : '1x'}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<span className="time">{formatTime(audioState.duration)}</span>
|
||||
</div>
|
||||
@@ -173,6 +212,19 @@ const AudioPlayer: React.FC<AudioPlayerProps> = forwardRef((props, ref) => {
|
||||
></IconFont>
|
||||
}
|
||||
></Button>
|
||||
{speakerOn && (
|
||||
<Slider
|
||||
tooltip={{ formatter: handleFormatVolume }}
|
||||
style={{ height: '100px' }}
|
||||
className="volume-slider"
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={volume}
|
||||
vertical
|
||||
onChange={handleVolumeChange}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<audio
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Button } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
interface CheckButtonsProps {
|
||||
options: Global.BaseOption<string | number>[];
|
||||
onChange: (value: string | number) => void;
|
||||
cancelable?: boolean;
|
||||
size?: 'small' | 'middle' | 'large';
|
||||
type?: 'text' | 'primary' | 'default' | 'dashed' | 'link' | undefined;
|
||||
}
|
||||
|
||||
const CheckButtons: React.FC<CheckButtonsProps> = (props) => {
|
||||
const [type, setType] = React.useState(props.type || 'text');
|
||||
const [active, setActive] = React.useState<string | number | null>(null);
|
||||
const handleChange = (value: string | number) => {
|
||||
props.onChange(value);
|
||||
if (props.cancelable && active === value) {
|
||||
setActive(null);
|
||||
} else {
|
||||
setActive(value);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="flex-center gap-6">
|
||||
{props.options?.map?.((option, index) => {
|
||||
return (
|
||||
<Button
|
||||
size={props.size}
|
||||
key={option.value}
|
||||
onClick={() => handleChange(option.value)}
|
||||
variant="filled"
|
||||
color={active === option.value ? 'default' : undefined}
|
||||
type={type}
|
||||
>
|
||||
{option.label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(CheckButtons);
|
||||
@@ -7,7 +7,6 @@ interface SpeechContentProps {
|
||||
}
|
||||
|
||||
const SpeechContent: React.FC<SpeechContentProps> = (props) => {
|
||||
console.log('SpeechContent', props);
|
||||
return (
|
||||
<>
|
||||
{props.dataList.map((item) => (
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { DownloadOutlined, PlayCircleOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
DownloadOutlined,
|
||||
PauseCircleOutlined,
|
||||
PlayCircleOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import dayjs from 'dayjs';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import AudioPlayer from './audio-player';
|
||||
import './styles/index.less';
|
||||
|
||||
// const audioUrl = require('./ih.mp4');
|
||||
const audioFormat = {
|
||||
'audio/mpeg': 'mp3',
|
||||
'audio/wav': 'wav',
|
||||
'audio/ogg': 'ogg',
|
||||
'audio/webm': 'webm',
|
||||
'audio/aac': 'aac',
|
||||
'audio/x-flac': 'flac',
|
||||
'audio/pcm': 'pcm',
|
||||
'audio/flac': 'flac',
|
||||
'audio/x-wav': 'wav',
|
||||
'audio/L16': 'pcm',
|
||||
'audio/opus': 'opus'
|
||||
};
|
||||
|
||||
interface SpeechContentProps {
|
||||
prompt: string;
|
||||
@@ -16,12 +34,19 @@ interface SpeechContentProps {
|
||||
audioUrl: string;
|
||||
}
|
||||
const SpeechItem: React.FC<SpeechContentProps> = (props) => {
|
||||
console.log('porps=======', props);
|
||||
const intl = useIntl();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [isPlay, setIsPlay] = useState(false);
|
||||
const ref = useRef<HTMLAudioElement>(null);
|
||||
|
||||
const handlePlay = () => {
|
||||
if (isPlay) {
|
||||
ref.current?.pause();
|
||||
setIsPlay(false);
|
||||
return;
|
||||
}
|
||||
ref.current?.play();
|
||||
setIsPlay(true);
|
||||
};
|
||||
|
||||
const handleCollapse = () => {
|
||||
@@ -30,7 +55,7 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
|
||||
|
||||
const onDownload = () => {
|
||||
const url = props.audioUrl || '';
|
||||
const filename = Date.now() + '';
|
||||
const filename = `audio-${dayjs().format('YYYYMMDDHHmmss')}.${props.format}`;
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
@@ -45,7 +70,6 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
|
||||
<div className="speech-item">
|
||||
<div className="voice">
|
||||
<IconFont type="icon-user_voice" className="font-size-16" />
|
||||
{/* <span className="text">{props.voice}</span> */}
|
||||
</div>
|
||||
<div className="wrapper">
|
||||
<AudioPlayer
|
||||
@@ -62,15 +86,25 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
|
||||
<span className="item">{props.speed}x</span>
|
||||
</span>
|
||||
<div className="actions">
|
||||
<Tooltip title="Play">
|
||||
<Tooltip
|
||||
title={
|
||||
isPlay
|
||||
? intl.formatMessage({ id: 'playground.audio.button.stop' })
|
||||
: intl.formatMessage({ id: 'playground.audio.button.play' })
|
||||
}
|
||||
>
|
||||
<Button
|
||||
onClick={handlePlay}
|
||||
icon={<PlayCircleOutlined />}
|
||||
icon={isPlay ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
|
||||
type="text"
|
||||
size="small"
|
||||
></Button>
|
||||
</Tooltip>
|
||||
<Tooltip title="Download">
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id: 'playground.audio.button.download'
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
onClick={onDownload}
|
||||
icon={<DownloadOutlined />}
|
||||
@@ -78,21 +112,8 @@ const SpeechItem: React.FC<SpeechContentProps> = (props) => {
|
||||
size="small"
|
||||
></Button>
|
||||
</Tooltip>
|
||||
{/* <Tooltip title="Show Prompt">
|
||||
<Button
|
||||
icon={<FileTextOutlined />}
|
||||
type="text"
|
||||
size="small"
|
||||
onClick={handleCollapse}
|
||||
></Button>
|
||||
</Tooltip> */}
|
||||
</div>
|
||||
</div>
|
||||
{/* {collapsed && (
|
||||
<div className="prompt-box">
|
||||
<div className="prompt">{props.prompt}</div>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,12 +13,11 @@ interface UploadAudioProps {
|
||||
const UploadAudio: React.FC<UploadAudioProps> = (props) => {
|
||||
const intl = useIntl();
|
||||
const beforeUpload = (file: any) => {
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleOnChange = React.useCallback(
|
||||
(data: { file: any; fileList: any }) => {
|
||||
console.log('handleOnChange', data);
|
||||
props.onChange?.(data);
|
||||
},
|
||||
[]
|
||||
|
||||
@@ -88,7 +88,10 @@ export default {
|
||||
'playground.audio.generating.tips': 'Generated text will appear here.',
|
||||
'playground.audio.uploadfile.tips':
|
||||
'Please upload an audio file, supported formats: {formats}',
|
||||
'playground.input.multiplePaste': 'Multi-line paste',
|
||||
'playground.input.multiplePaste': 'Batch Input Mode',
|
||||
'playground.input.multiplePaste.tips':
|
||||
'When enabled, pasted multi-line text will be automatically split by newline into separate entries in the form.',
|
||||
'playground.audio.button.generate': 'Generate Text Content',
|
||||
'playground.multiple.on': 'Enable',
|
||||
'playground.multiple.off': 'Disable',
|
||||
'playground.image.params.sampler': 'Sampler',
|
||||
@@ -96,12 +99,15 @@ export default {
|
||||
'playground.image.params.seed': 'Seed',
|
||||
'playground.image.params.negativePrompt': 'Negative Prompt',
|
||||
'playground.image.params.cfgScale': 'Scale Factor',
|
||||
'playground.image.params.custom': 'Custom',
|
||||
'playground.image.params.custom.tips': 'Parameter definition',
|
||||
'playground.image.params.custom': 'Advanced',
|
||||
'playground.image.params.custom.tips': 'API Style',
|
||||
'playground.image.params.openai': 'OpenAI Compatible',
|
||||
'playground.embedding.handler.tips': 'Resize Height',
|
||||
'playground.embedding.pcatips1':
|
||||
'PCA is used to reduce the dimensionality of document vectors, projecting new data into PCA space.',
|
||||
'PCA (Principal Component Analysis) is used to reduce the dimensionality of embedding vectors, making them easier to visualize.',
|
||||
'playground.embedding.pcatips2':
|
||||
'In the chart, the distance between points represents the similarity between documents.'
|
||||
'In the chart, the distance between points indicates the similarity between the corresponding documents. Closer points mean higher similarity.',
|
||||
'playground.audio.button.play': 'Play',
|
||||
'playground.audio.button.download': 'Download',
|
||||
'playground.audio.button.stop': 'Stop'
|
||||
};
|
||||
|
||||
@@ -86,20 +86,25 @@ export default {
|
||||
'playground.audio.generating.tips': '生成的文本将出现在这里',
|
||||
'playground.audio.uploadfile.tips': '请上传音频文件,支持格式:{formats}',
|
||||
'playground.audio.button.generate': '生成文本',
|
||||
'playground.input.multiplePaste': '多行粘贴',
|
||||
'playground.input.multiplePaste': '批量输入',
|
||||
'playground.input.multiplePaste.tips':
|
||||
'启用后,粘贴的多行文本将自动按换行符分割为表单中的单独条目。',
|
||||
'playground.multiple.on': '开启',
|
||||
'playground.multiple.off': '关闭',
|
||||
'playground.image.params.sampler': '采样器',
|
||||
'playground.image.params.samplerSteps': '采样器步数',
|
||||
'playground.image.params.sampler': '采样方法',
|
||||
'playground.image.params.samplerSteps': '迭代步数',
|
||||
'playground.image.params.seed': '随机种子',
|
||||
'playground.image.params.negativePrompt': '负面提示',
|
||||
'playground.image.params.cfgScale': '缩放因子',
|
||||
'playground.image.params.custom': '自定义',
|
||||
'playground.image.params.custom.tips': '参数定义',
|
||||
'playground.image.params.negativePrompt': '负向提示',
|
||||
'playground.image.params.cfgScale': '提示词引导系数',
|
||||
'playground.image.params.custom': '高级',
|
||||
'playground.image.params.custom.tips': 'API 风格',
|
||||
'playground.image.params.openai': 'OpenAI 兼容',
|
||||
'playground.embedding.handler.tips': '高度调节',
|
||||
'playground.embedding.pcatips1':
|
||||
'采用主成分分析(PCA)对文档向量化后的数据降维,将新数据投射到PCA 空间中。',
|
||||
'PCA(主成分分析)用于降低嵌入向量的维数,使它们更容易可视化。',
|
||||
'playground.embedding.pcatips2':
|
||||
'图表中,点之间的距离表示对应文档之间的相似度。'
|
||||
'在图表中,点之间的距离表示相应文档之间的相似度。点越近意味着相似度越高。',
|
||||
'playground.audio.button.play': '播放',
|
||||
'playground.audio.button.download': '下载',
|
||||
'playground.audio.button.stop': '停止'
|
||||
};
|
||||
|
||||
@@ -450,7 +450,7 @@ const Models: React.FC<ModelsProps> = ({
|
||||
}}
|
||||
color="geekblue"
|
||||
>
|
||||
Embedding Only
|
||||
Embedding
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
@@ -464,7 +464,7 @@ const Models: React.FC<ModelsProps> = ({
|
||||
}}
|
||||
color="geekblue"
|
||||
>
|
||||
{intl.formatMessage({ id: 'playground.audio.texttospeech' })}
|
||||
Text-To-Speech
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
@@ -478,7 +478,7 @@ const Models: React.FC<ModelsProps> = ({
|
||||
}}
|
||||
color="geekblue"
|
||||
>
|
||||
{intl.formatMessage({ id: 'playground.audio.speechtotext' })}
|
||||
Speech-To-Text
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
@@ -492,7 +492,7 @@ const Models: React.FC<ModelsProps> = ({
|
||||
}}
|
||||
color="geekblue"
|
||||
>
|
||||
Image Only
|
||||
Image
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -99,25 +99,17 @@ export const textToSpeech = async (params: any, options?: any) => {
|
||||
|
||||
const audioBlob = await res.blob();
|
||||
const audioUrl = URL.createObjectURL(audioBlob);
|
||||
return audioUrl;
|
||||
return {
|
||||
url: audioUrl,
|
||||
type: audioBlob.type
|
||||
};
|
||||
};
|
||||
|
||||
// export const speechToText = async (params: any, options?: any) => {
|
||||
// const res = await fetch(AUDIO_SPEECH_TO_TEXT_API, {
|
||||
// method: 'POST',
|
||||
// body: JSON.stringify(params.data),
|
||||
// signal: params.signal
|
||||
// });
|
||||
// if (!res.ok) {
|
||||
// throw new Error('Network response was not ok');
|
||||
// }
|
||||
// return res.json();
|
||||
// };
|
||||
|
||||
export const speechToText = async (params: any, options?: any) => {
|
||||
return request(AUDIO_SPEECH_TO_TEXT_API, {
|
||||
method: 'POST',
|
||||
data: params.data,
|
||||
cancelToken: options?.cancelToken,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { AudioOutlined } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Space, Tooltip } from 'antd';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
// import '../style/audio-input.less';
|
||||
|
||||
interface AudioInputProps {
|
||||
@@ -9,6 +15,7 @@ interface AudioInputProps {
|
||||
chunks: any[];
|
||||
url: string;
|
||||
name: string;
|
||||
type: string;
|
||||
duration: number;
|
||||
}) => void;
|
||||
onAnalyse?: (analyseData: any, frequencyBinCount: any) => void;
|
||||
@@ -19,6 +26,11 @@ interface AudioInputProps {
|
||||
type?: 'text' | 'primary' | 'default';
|
||||
}
|
||||
|
||||
const recordingFormat = {
|
||||
type: 'audio/wav',
|
||||
suffix: '.wav'
|
||||
};
|
||||
|
||||
const AudioInput: React.FC<AudioInputProps> = (props) => {
|
||||
const intl = useIntl();
|
||||
const [audioOn, setAudioOn] = useState(false);
|
||||
@@ -155,6 +167,8 @@ const AudioInput: React.FC<AudioInputProps> = (props) => {
|
||||
|
||||
try {
|
||||
await EnableAudio();
|
||||
console.log('audioStream:', audioStream.current);
|
||||
|
||||
audioRecorder.current = new MediaRecorder(audioStream.current);
|
||||
|
||||
const audioChunks: any[] = [];
|
||||
@@ -170,14 +184,14 @@ const AudioInput: React.FC<AudioInputProps> = (props) => {
|
||||
|
||||
// stop recording
|
||||
audioRecorder.current.onstop = () => {
|
||||
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
|
||||
const audioBlob = new Blob(audioChunks, { type: recordingFormat.type });
|
||||
const audioUrl = URL.createObjectURL(audioBlob);
|
||||
handleAudioData({
|
||||
chunks: audioBlob,
|
||||
size: audioBlob.size,
|
||||
type: audioBlob.type,
|
||||
url: audioUrl,
|
||||
name: `recording-${new Date().toISOString()}.wav`,
|
||||
name: `recording-${new Date().toISOString()}${recordingFormat.suffix}`,
|
||||
duration: Math.ceil((Date.now() - startTime.current) / 1000)
|
||||
});
|
||||
|
||||
@@ -195,6 +209,25 @@ const AudioInput: React.FC<AudioInputProps> = (props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const renderRecordButtonTips = useMemo(() => {
|
||||
if (!audioPermission) {
|
||||
return intl.formatMessage({ id: 'playground.audio.enablemic' });
|
||||
}
|
||||
return isRecording
|
||||
? intl.formatMessage({ id: 'playground.audio.stoprecord' })
|
||||
: intl.formatMessage({ id: 'playground.audio.startrecord' });
|
||||
}, [audioPermission, isRecording, intl]);
|
||||
|
||||
const noAudioPermissionButtonStyle = useMemo(() => {
|
||||
if (!audioPermission) {
|
||||
return {
|
||||
backgroundColor: 'var(--ant-color-error-bg-filled-hover)',
|
||||
color: 'var(--ant-color-error-border-hover)',
|
||||
border: 'none'
|
||||
};
|
||||
}
|
||||
}, [audioPermission]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
handleStopRecording();
|
||||
@@ -210,13 +243,7 @@ const AudioInput: React.FC<AudioInputProps> = (props) => {
|
||||
<div className="audio-input">
|
||||
<Space size={40} className="btns">
|
||||
{
|
||||
<Tooltip
|
||||
title={
|
||||
isRecording
|
||||
? intl.formatMessage({ id: 'playground.audio.stoprecord' })
|
||||
: intl.formatMessage({ id: 'playground.audio.startrecord' })
|
||||
}
|
||||
>
|
||||
<Tooltip title={renderRecordButtonTips}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
@@ -231,6 +258,9 @@ const AudioInput: React.FC<AudioInputProps> = (props) => {
|
||||
icon={<AudioOutlined />}
|
||||
size="middle"
|
||||
type={props.type ?? 'text'}
|
||||
style={{
|
||||
...noAudioPermissionButtonStyle
|
||||
}}
|
||||
danger={isRecording}
|
||||
onClick={StartRecording}
|
||||
></Button>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
SendOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { useIntl, useSearchParams } from '@umijs/max';
|
||||
import { Button, Checkbox, Segmented, Tabs, Tooltip } from 'antd';
|
||||
import { Button, Segmented, Tabs, Tooltip } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { PCA } from 'ml-pca';
|
||||
import 'overlayscrollbars/overlayscrollbars.css';
|
||||
@@ -81,7 +81,7 @@ const GroundEmbedding: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
copyValue: ''
|
||||
});
|
||||
const [lessTwoInput, setLessTwoInput] = useState<boolean>(false);
|
||||
const multiplePasteEnable = useRef<boolean>(true);
|
||||
const [multiplePasteEnable, setMultiplePasteEnable] = useState<boolean>(true);
|
||||
|
||||
const [textList, setTextList] = useState<
|
||||
{ text: string; uid: number | string; name: string }[]
|
||||
@@ -279,7 +279,7 @@ const GroundEmbedding: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
|
||||
const handleOnPaste = useCallback(
|
||||
(e: any, index: number) => {
|
||||
if (!multiplePasteEnable.current) return;
|
||||
if (!multiplePasteEnable) return;
|
||||
const text = e.clipboardData.getData('text');
|
||||
if (text) {
|
||||
const dataLlist = text.split('\n').map((item: string) => {
|
||||
@@ -402,18 +402,26 @@ const GroundEmbedding: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
</div>
|
||||
</h3>
|
||||
<div className="flex-center gap-10">
|
||||
<Button className="flex-center" size="middle">
|
||||
<Checkbox
|
||||
defaultChecked={multiplePasteEnable.current}
|
||||
onChange={(e: any) => {
|
||||
multiplePasteEnable.current = e.target.checked;
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id: 'playground.input.multiplePaste.tips'
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
className="flex-center"
|
||||
variant="filled"
|
||||
size="middle"
|
||||
color={multiplePasteEnable ? 'primary' : 'default'}
|
||||
onClick={() => {
|
||||
setMultiplePasteEnable(!multiplePasteEnable);
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: 'playground.input.multiplePaste'
|
||||
})}
|
||||
</Checkbox>
|
||||
</Button>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Button size="middle" onClick={handleAddText}>
|
||||
<PlusOutlined />
|
||||
{intl.formatMessage({ id: 'playground.embedding.addtext' })}
|
||||
|
||||
@@ -47,14 +47,17 @@ interface MessageProps {
|
||||
const initialValues = {
|
||||
n: 1,
|
||||
size: '512x512',
|
||||
quality: 'standard',
|
||||
style: null
|
||||
seed: null,
|
||||
sampler: 'euler_a',
|
||||
cfg_scale: 4.5,
|
||||
sample_steps: 10,
|
||||
negative_prompt: null
|
||||
};
|
||||
|
||||
const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
const { modelList } = props;
|
||||
const messageId = useRef<number>(0);
|
||||
const [isOpenaiCompatible, setIsOpenaiCompatible] = useState<boolean>(true);
|
||||
const [isOpenaiCompatible, setIsOpenaiCompatible] = useState<boolean>(false);
|
||||
const [imageList, setImageList] = useState<
|
||||
{
|
||||
dataUrl: string;
|
||||
@@ -223,7 +226,7 @@ const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
const params = {
|
||||
stream: true,
|
||||
stream_options: {
|
||||
chunk_result: true
|
||||
// chunk_result: false
|
||||
},
|
||||
prompt: current?.content || currentPrompt || '',
|
||||
..._.omitBy(finalParameters, (value: string) => !value)
|
||||
@@ -251,8 +254,6 @@ const GroundImages: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
const imgItem = newImageList[item.index];
|
||||
if (item.b64_json) {
|
||||
imgItem.dataUrl += item.b64_json;
|
||||
// imgItem.cache.push(item.b64_json);
|
||||
console.log('imgItem.dataUrl:', imgItem.dataUrl);
|
||||
}
|
||||
const progress = _.round(item.progress, 0);
|
||||
newImageList[item.index] = {
|
||||
|
||||
@@ -303,7 +303,6 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
clearAll={handleClear}
|
||||
setModelSelections={handleSelectModel}
|
||||
presetPrompt={handlePresetPrompt}
|
||||
modelList={modelList}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import useOverlayScroller from '@/hooks/use-overlay-scroller';
|
||||
import useRequestToken from '@/hooks/use-request-token';
|
||||
import { ClearOutlined, PlusOutlined, SendOutlined } from '@ant-design/icons';
|
||||
import { useIntl, useSearchParams } from '@umijs/max';
|
||||
import { Button, Checkbox, Input, Spin, Tag } from 'antd';
|
||||
import { Button, Input, Spin, Tag, Tooltip } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import 'overlayscrollbars/overlayscrollbars.css';
|
||||
@@ -77,7 +77,7 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
const messageListLengthCache = useRef<number>(0);
|
||||
const requestToken = useRef<any>(null);
|
||||
const formRef = useRef<any>(null);
|
||||
const multiplePasteEnable = useRef<boolean>(true);
|
||||
const [multiplePasteEnable, setMultiplePasteEnable] = useState<boolean>(true);
|
||||
const [fileList, setFileList] = useState<
|
||||
{
|
||||
text: string;
|
||||
@@ -346,7 +346,7 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
|
||||
const handleOnPaste = useCallback(
|
||||
(e: any, index: number) => {
|
||||
if (!multiplePasteEnable.current) return;
|
||||
if (!multiplePasteEnable) return;
|
||||
const text = e.clipboardData.getData('text');
|
||||
if (text) {
|
||||
console.log('text:', text);
|
||||
@@ -465,18 +465,25 @@ const GroundReranker: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
)}
|
||||
</span>
|
||||
<div className="flex-center gap-10">
|
||||
<Button className="flex-center" size="middle">
|
||||
<Checkbox
|
||||
defaultChecked={multiplePasteEnable.current}
|
||||
onChange={(e: any) => {
|
||||
multiplePasteEnable.current = e.target.checked;
|
||||
<Tooltip
|
||||
title={intl.formatMessage({
|
||||
id: 'playground.input.multiplePaste.tips'
|
||||
})}
|
||||
>
|
||||
<Button
|
||||
className="flex-center"
|
||||
variant="filled"
|
||||
size="middle"
|
||||
color={multiplePasteEnable ? 'primary' : 'default'}
|
||||
onClick={() => {
|
||||
setMultiplePasteEnable(!multiplePasteEnable);
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage({
|
||||
id: 'playground.input.multiplePaste'
|
||||
})}
|
||||
</Checkbox>
|
||||
</Button>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button size="middle" onClick={handleAddText}>
|
||||
<PlusOutlined />
|
||||
{intl.formatMessage({ id: 'playground.embedding.addtext' })}
|
||||
|
||||
@@ -4,9 +4,9 @@ import IconFont from '@/components/icon-font';
|
||||
import UploadAudio from '@/components/upload-audio';
|
||||
import useOverlayScroller from '@/hooks/use-overlay-scroller';
|
||||
import { readAudioFile } from '@/utils/load-audio-file';
|
||||
import { AudioOutlined, SendOutlined } from '@ant-design/icons';
|
||||
import { SendOutlined } from '@ant-design/icons';
|
||||
import { useIntl, useSearchParams } from '@umijs/max';
|
||||
import { Button, Spin, Tag, Tooltip } from 'antd';
|
||||
import { Button, Spin, Tooltip } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import 'overlayscrollbars/overlayscrollbars.css';
|
||||
import {
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
} from 'react';
|
||||
import { speechToText } from '../apis';
|
||||
import { RealtimeParamsConfig as paramsConfig } from '../config/params-config';
|
||||
import { MessageItem } from '../config/types';
|
||||
import '../style/ground-left.less';
|
||||
import '../style/speech-to-text.less';
|
||||
import '../style/system-message-wrap.less';
|
||||
@@ -42,14 +41,9 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
const intl = useIntl();
|
||||
const { modelList } = props;
|
||||
const messageId = useRef<number>(0);
|
||||
const [messageList, setMessageList] = useState<MessageItem[]>([
|
||||
{
|
||||
content: '',
|
||||
title: '',
|
||||
role: '',
|
||||
uid: messageId.current
|
||||
}
|
||||
]);
|
||||
const [messageList, setMessageList] = useState<
|
||||
{ uid: number; content: string }[]
|
||||
>([]);
|
||||
const [searchParams] = useSearchParams();
|
||||
const selectModel = searchParams.get('model') || '';
|
||||
const [parameters, setParams] = useState<any>({});
|
||||
@@ -68,7 +62,6 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
analyser: null
|
||||
});
|
||||
const [isRecording, setIsRecording] = useState(false);
|
||||
const [recordEnd, setRecordEnd] = useState(false);
|
||||
const formRef = useRef<any>(null);
|
||||
|
||||
const { initialize, updateScrollerPosition } = useOverlayScroller();
|
||||
@@ -127,8 +120,6 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
setMessageList([
|
||||
{
|
||||
content: result.text,
|
||||
title: '',
|
||||
role: '',
|
||||
uid: messageId.current
|
||||
}
|
||||
]);
|
||||
@@ -136,7 +127,6 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
console.log('error:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setRecordEnd(false);
|
||||
setIsRecording(false);
|
||||
}
|
||||
};
|
||||
@@ -170,9 +160,6 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
duration: data.duration
|
||||
};
|
||||
});
|
||||
setTimeout(() => {
|
||||
setRecordEnd(true);
|
||||
}, 200);
|
||||
},
|
||||
[]
|
||||
);
|
||||
@@ -183,9 +170,8 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
|
||||
const handleUploadChange = useCallback(
|
||||
async (data: { file: any; fileList: any }) => {
|
||||
const res = await readAudioFile(data.file.originFileObj);
|
||||
const res = await readAudioFile(data.file);
|
||||
setAudioData(res);
|
||||
setRecordEnd(true);
|
||||
},
|
||||
[]
|
||||
);
|
||||
@@ -208,15 +194,16 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
submitMessage();
|
||||
};
|
||||
|
||||
const handleOnDiscard = useCallback(() => {
|
||||
setRecordEnd(false);
|
||||
setAudioData(null);
|
||||
setIsRecording(false);
|
||||
}, []);
|
||||
|
||||
const renderAniamtion = () => {
|
||||
if (!audioPermissionOn) {
|
||||
return null;
|
||||
return (
|
||||
<div className="tips-text">
|
||||
<IconFont type={'icon-audio'} style={{ fontSize: 20 }}></IconFont>
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'playground.audio.enablemic' })}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (isRecording) {
|
||||
return (
|
||||
@@ -240,7 +227,7 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
useEffect(() => {
|
||||
console.log('parameters:', parameters);
|
||||
}, [parameters]);
|
||||
useEffect(() => {}, [messageList]);
|
||||
|
||||
useEffect(() => {
|
||||
if (scroller.current) {
|
||||
initialize(scroller.current);
|
||||
@@ -259,13 +246,6 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
}
|
||||
}, [messageList, loading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (messageList.length > messageListLengthCache.current) {
|
||||
updateScrollerPosition();
|
||||
}
|
||||
messageListLengthCache.current = messageList.length;
|
||||
}, [messageList.length]);
|
||||
|
||||
return (
|
||||
<div className="ground-left-wrapper">
|
||||
<div className="ground-left">
|
||||
@@ -303,42 +283,6 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
renderAniamtion()
|
||||
)}
|
||||
</div>
|
||||
{!audioPermissionOn && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%'
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<Tag
|
||||
style={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
lineHeight: '36px',
|
||||
textAlign: 'center'
|
||||
}}
|
||||
bordered={false}
|
||||
color="error"
|
||||
icon={
|
||||
<AudioOutlined className="font-size-16"></AudioOutlined>
|
||||
}
|
||||
></Tag>
|
||||
</span>
|
||||
<span
|
||||
style={{
|
||||
marginTop: 10,
|
||||
fontSize: 14,
|
||||
fontWeight: 500
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage({ id: 'playground.audio.enablemic' })}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
@@ -365,7 +309,7 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
{audioData ? (
|
||||
{messageList.length ? (
|
||||
messageList[0]?.content
|
||||
) : (
|
||||
<span className="text-tertiary">
|
||||
|
||||
@@ -36,8 +36,7 @@ interface MessageProps {
|
||||
|
||||
const initialValues = {
|
||||
voice: '',
|
||||
response_format: 'mp3',
|
||||
speed: 1
|
||||
response_format: 'mp3'
|
||||
};
|
||||
|
||||
const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
@@ -113,13 +112,13 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
...parameters,
|
||||
input: current?.content || currentPrompt
|
||||
};
|
||||
const audioUrl: any = await textToSpeech({
|
||||
const res: any = await textToSpeech({
|
||||
data: params,
|
||||
url: CHAT_API,
|
||||
signal
|
||||
});
|
||||
|
||||
console.log('result:', parameters, audioUrl);
|
||||
console.log('result:', res);
|
||||
|
||||
setMessageList([
|
||||
{
|
||||
@@ -129,7 +128,7 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
|
||||
speed: parameters.speed,
|
||||
uid: messageId.current,
|
||||
autoplay: checkvalueRef.current,
|
||||
audioUrl: audioUrl
|
||||
audioUrl: res.url
|
||||
}
|
||||
]);
|
||||
} catch (error) {
|
||||
|
||||
@@ -229,8 +229,8 @@ const ParamsSettings: React.FC<ParamsSettingsProps> = ({
|
||||
variant="borderless"
|
||||
>
|
||||
<Slider
|
||||
defaultValue={1024}
|
||||
max={2048}
|
||||
defaultValue={2048}
|
||||
max={16 * 1024}
|
||||
step={1}
|
||||
style={{ marginBottom: 0, marginTop: 16, marginInline: 0 }}
|
||||
tooltip={{ open: false }}
|
||||
|
||||
@@ -28,11 +28,11 @@ export const TTSParamsConfig: ParamsSchema[] = [
|
||||
name: 'response_format',
|
||||
options: [
|
||||
{ label: 'mp3', value: 'mp3' },
|
||||
{ label: 'opus', value: 'opus' },
|
||||
{ label: 'aac', value: 'aac' },
|
||||
{ label: 'flac', value: 'flac' },
|
||||
{ label: 'wav', value: 'wav' },
|
||||
{ label: 'pcm', value: 'pcm' }
|
||||
// { label: 'opus', value: 'opus' },
|
||||
// { label: 'aac', value: 'aac' },
|
||||
// { label: 'flac', value: 'flac' },
|
||||
{ label: 'wav', value: 'wav' }
|
||||
// { label: 'pcm', value: 'pcm' }
|
||||
],
|
||||
label: {
|
||||
text: 'playground.params.format',
|
||||
@@ -43,27 +43,27 @@ export const TTSParamsConfig: ParamsSchema[] = [
|
||||
required: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'Select',
|
||||
name: 'speed',
|
||||
options: [
|
||||
{ label: '0.25x', value: 0.25 },
|
||||
{ label: '0.5x', value: 0.5 },
|
||||
{ label: '1x', value: 1 },
|
||||
{ label: '2x', value: 2 },
|
||||
{ label: '4x', value: 4 }
|
||||
],
|
||||
label: {
|
||||
text: 'playground.params.speed',
|
||||
isLocalized: true
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
required: false
|
||||
}
|
||||
]
|
||||
}
|
||||
// {
|
||||
// type: 'Select',
|
||||
// name: 'speed',
|
||||
// options: [
|
||||
// { label: '0.25x', value: 0.25 },
|
||||
// { label: '0.5x', value: 0.5 },
|
||||
// { label: '1x', value: 1 },
|
||||
// { label: '2x', value: 2 },
|
||||
// { label: '4x', value: 4 }
|
||||
// ],
|
||||
// label: {
|
||||
// text: 'playground.params.speed',
|
||||
// isLocalized: true
|
||||
// },
|
||||
// rules: [
|
||||
// {
|
||||
// required: false
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
];
|
||||
|
||||
export const RealtimeParamsConfig: ParamsSchema[] = [
|
||||
|
||||
@@ -86,45 +86,62 @@ export const readStreamData = async (
|
||||
await readStreamData(reader, decoder, callback);
|
||||
};
|
||||
|
||||
// Process the remainder of the buffer
|
||||
const processBuffer = (buffer: string, callback: (data: any) => void) => {
|
||||
const lines = buffer.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const jsonStr = line.slice(6).trim();
|
||||
try {
|
||||
const jsonData = JSON.parse(jsonStr);
|
||||
callback(jsonData);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
'Failed to parse JSON from remaining buffer:',
|
||||
jsonStr,
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const readLargeStreamData = async (
|
||||
reader: any,
|
||||
decoder: TextDecoder,
|
||||
callback: (data: any) => void
|
||||
) => {
|
||||
let buffer = '';
|
||||
let buffer = ''; // cache incomplete line
|
||||
|
||||
const processStream = async () => {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
if (buffer) {
|
||||
try {
|
||||
extractJSON(buffer).forEach((data) => {
|
||||
callback?.(data);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('parse buffer failed:', buffer);
|
||||
}
|
||||
// Process remaining buffered data
|
||||
if (buffer.trim()) {
|
||||
processBuffer(buffer, callback);
|
||||
}
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
// cache each chunk
|
||||
// Decode new chunk of data and append to buffer
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
|
||||
const extractedData = extractJSON(buffer);
|
||||
// Try to process the complete line in the buffer
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || ''; // Keep last line (may be incomplete)
|
||||
|
||||
extractedData.forEach((data) => {
|
||||
callback?.(data);
|
||||
});
|
||||
|
||||
const lastIndex = buffer.lastIndexOf('}');
|
||||
buffer = lastIndex !== -1 ? buffer.slice(lastIndex + 1) : buffer;
|
||||
|
||||
// next chunk
|
||||
await processStream();
|
||||
};
|
||||
|
||||
await processStream();
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
const jsonStr = line.slice(6).trim();
|
||||
try {
|
||||
const jsonData = JSON.parse(jsonStr);
|
||||
callback(jsonData);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse JSON:', jsonStr, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const readTextEventStreamData = async (
|
||||
|
||||
@@ -32,6 +32,7 @@ export const loadAudioData = async (data: any, type: string) => {
|
||||
};
|
||||
|
||||
export const readAudioFile = async (file: File) => {
|
||||
console.log('file====', file);
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async function (e: any) {
|
||||
|
||||
Reference in New Issue
Block a user