fix: edit message add image
This commit is contained in:
@@ -87,4 +87,4 @@ const UploadAudio: React.FC<UploadAudioProps> = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(UploadAudio);
|
export default UploadAudio;
|
||||||
|
|||||||
@@ -201,9 +201,11 @@ const GroundSTT: React.FC<MessageProps> = forwardRef((props, ref) => {
|
|||||||
|
|
||||||
const handleUploadChange = useCallback(
|
const handleUploadChange = useCallback(
|
||||||
async (data: { file: any; fileList: any }) => {
|
async (data: { file: any; fileList: any }) => {
|
||||||
const res = await readAudioFile(data.file);
|
try {
|
||||||
setAudioData(res);
|
const res = await readAudioFile(data.file);
|
||||||
setTokenResult(null);
|
setAudioData(res);
|
||||||
|
setTokenResult(null);
|
||||||
|
} catch (error) {}
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import SimpleAudio from '@/components/audio-player/simple-audio';
|
import SimpleAudio from '@/components/audio-player/simple-audio';
|
||||||
import DropDownActions from '@/components/drop-down-actions';
|
|
||||||
import IconFont from '@/components/icon-font';
|
import IconFont from '@/components/icon-font';
|
||||||
import UploadAudio from '@/components/upload-audio';
|
import UploadAudio from '@/components/upload-audio';
|
||||||
import HotKeys, { KeyMap } from '@/config/hotkeys';
|
import HotKeys, { KeyMap } from '@/config/hotkeys';
|
||||||
@@ -11,11 +10,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
ClearOutlined,
|
ClearOutlined,
|
||||||
CustomerServiceOutlined,
|
CustomerServiceOutlined,
|
||||||
LinkOutlined,
|
|
||||||
PictureOutlined,
|
|
||||||
SendOutlined,
|
SendOutlined,
|
||||||
SwapOutlined,
|
SwapOutlined
|
||||||
UploadOutlined
|
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Checkbox, Divider, Input, Tooltip } from 'antd';
|
import { Button, Checkbox, Divider, Input, Tooltip } from 'antd';
|
||||||
@@ -32,9 +28,9 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Roles } from '../config';
|
import { Roles } from '../config';
|
||||||
import { AudioFormat, MessageItem } from '../config/types';
|
import { AudioFormat, MessageItem } from '../config/types';
|
||||||
|
import useAddImage from '../hooks/use-add-image';
|
||||||
import '../style/message-input.less';
|
import '../style/message-input.less';
|
||||||
import ThumbImg from './thumb-img';
|
import ThumbImg from './thumb-img';
|
||||||
import UploadImg from './upload-img';
|
|
||||||
|
|
||||||
const AudioWrapper = styled.div`
|
const AudioWrapper = styled.div`
|
||||||
padding-block: 10px;
|
padding-block: 10px;
|
||||||
@@ -300,39 +296,6 @@ const MessageInput: React.FC<MessageInputProps> = forwardRef(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInputImageUrl = (e: any) => {
|
|
||||||
const url = e.target.value;
|
|
||||||
// ends with .png, .jpg, .jpeg
|
|
||||||
const regex = /(https?:\/\/\S+\.(png|jpg|jpeg))/gi;
|
|
||||||
const isValid = regex.test(url);
|
|
||||||
setOpenImgTips(!isValid);
|
|
||||||
if (!isValid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleUpdateImgList([
|
|
||||||
{
|
|
||||||
uid: updateUidCount(),
|
|
||||||
dataUrl: e.target.value
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
setIsFromUrl(false);
|
|
||||||
setOpenImgTips(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnEscape = (e: any) => {
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
setIsFromUrl(false);
|
|
||||||
setOpenImgTips(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAddImgFromUrl = () => {
|
|
||||||
setIsFromUrl(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
inputImgRef.current?.focus?.();
|
|
||||||
}, 100);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUploadAudioChange = async (data: {
|
const handleUploadAudioChange = async (data: {
|
||||||
file: any;
|
file: any;
|
||||||
fileList: any[];
|
fileList: any[];
|
||||||
@@ -398,6 +361,11 @@ const MessageInput: React.FC<MessageInputProps> = forwardRef(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { ImageURLInput, UploadImageButton } = useAddImage({
|
||||||
|
handleUpdateImgList: handleUpdateImgList,
|
||||||
|
updateUidCount: updateUidCount
|
||||||
|
});
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleInputChange: handleInputChange
|
handleInputChange: handleInputChange
|
||||||
}));
|
}));
|
||||||
@@ -451,41 +419,9 @@ const MessageInput: React.FC<MessageInputProps> = forwardRef(
|
|||||||
{checkLabel}
|
{checkLabel}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
)}
|
)}
|
||||||
{actions.includes('upload') && message.role === Roles.User && (
|
{actions.includes('upload') &&
|
||||||
<DropDownActions
|
message.role === Roles.User &&
|
||||||
placement={'topLeft'}
|
UploadImageButton}
|
||||||
menu={{
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: (
|
|
||||||
<UploadImg
|
|
||||||
handleUpdateImgList={handleUpdateImgList}
|
|
||||||
size="middle"
|
|
||||||
>
|
|
||||||
{intl.formatMessage({ id: 'playground.img.upload' })}
|
|
||||||
</UploadImg>
|
|
||||||
),
|
|
||||||
key: 'upload_image',
|
|
||||||
icon: <UploadOutlined />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: intl.formatMessage({
|
|
||||||
id: 'playground.uploadImage.url.button'
|
|
||||||
}),
|
|
||||||
key: 'add_image_url',
|
|
||||||
icon: <LinkOutlined />,
|
|
||||||
onClick: handleAddImgFromUrl
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
size="middle"
|
|
||||||
icon={<PictureOutlined />}
|
|
||||||
></Button>
|
|
||||||
</DropDownActions>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{actions.includes('upload') && message.role === Roles.User && (
|
{actions.includes('upload') && message.role === Roles.User && (
|
||||||
<UploadAudio
|
<UploadAudio
|
||||||
@@ -532,6 +468,7 @@ const MessageInput: React.FC<MessageInputProps> = forwardRef(
|
|||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{ImageURLInput}
|
||||||
</div>
|
</div>
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
{actions.includes('add') && (
|
{actions.includes('add') && (
|
||||||
@@ -603,26 +540,7 @@ const MessageInput: React.FC<MessageInputProps> = forwardRef(
|
|||||||
</AudioWrapper>
|
</AudioWrapper>
|
||||||
)}
|
)}
|
||||||
</ImgsWrapper>
|
</ImgsWrapper>
|
||||||
{isFromUrl && (
|
|
||||||
<Tooltip
|
|
||||||
open={openImgTips}
|
|
||||||
title={intl.formatMessage({
|
|
||||||
id: 'playground.uploadImage.url.invalid'
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
ref={inputImgRef}
|
|
||||||
status={openImgTips ? 'error' : ''}
|
|
||||||
placeholder={intl.formatMessage({
|
|
||||||
id: 'playground.uploadImage.url.holder'
|
|
||||||
})}
|
|
||||||
style={{ width: 360 }}
|
|
||||||
onBlur={handleInputImageUrl}
|
|
||||||
onPressEnter={handleInputImageUrl}
|
|
||||||
onKeyDown={handleOnEscape}
|
|
||||||
></Input>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
<div className="input-box">
|
<div className="input-box">
|
||||||
{actions.includes('paste') ? (
|
{actions.includes('paste') ? (
|
||||||
<TextArea
|
<TextArea
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Tooltip } from 'antd';
|
import { Button, Tooltip } from 'antd';
|
||||||
|
import classNames from 'classnames';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Roles } from '../../config';
|
import { Roles } from '../../config';
|
||||||
@@ -20,7 +21,7 @@ import {
|
|||||||
MessageItem,
|
MessageItem,
|
||||||
MessageItemAction
|
MessageItemAction
|
||||||
} from '../../config/types';
|
} from '../../config/types';
|
||||||
import UploadImg from '../upload-img';
|
import useAddImage from '../../hooks/use-add-image';
|
||||||
|
|
||||||
interface MessageActionsProps {
|
interface MessageActionsProps {
|
||||||
data: MessageItem;
|
data: MessageItem;
|
||||||
@@ -57,32 +58,47 @@ const MessageActions: React.FC<MessageActionsProps> = ({
|
|||||||
file: any;
|
file: any;
|
||||||
fileList: any[];
|
fileList: any[];
|
||||||
}) => {
|
}) => {
|
||||||
const base64Audio = await convertFileToBase64(audio.file);
|
try {
|
||||||
const audioData = await readAudioFile(audio.file);
|
const base64Audio = await convertFileToBase64(audio.file);
|
||||||
updateMessage?.({
|
const audioData = await readAudioFile(audio.file);
|
||||||
role: data.role,
|
updateMessage?.({
|
||||||
content: data.content,
|
role: data.role,
|
||||||
uid: data.uid,
|
content: data.content,
|
||||||
imgs: data.imgs || [],
|
uid: data.uid,
|
||||||
audio: [
|
imgs: data.imgs || [],
|
||||||
{
|
audio: [
|
||||||
uid: audio.file.uid || audio.fileList[0].uid,
|
{
|
||||||
format: audioTypeMap[audio.file.type] as AudioFormat,
|
uid: audio.file.uid || audio.fileList[0].uid,
|
||||||
base64: base64Audio.split(',')[1],
|
format: audioTypeMap[audio.file.type] as AudioFormat,
|
||||||
data: _.pick(audioData, ['url', 'name', 'duration'])
|
base64: base64Audio.split(',')[1],
|
||||||
}
|
data: _.pick(audioData, ['url', 'name', 'duration'])
|
||||||
]
|
}
|
||||||
});
|
]
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log('error uploading audio file', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { ImageURLInput, UploadImageButton, isFromUrl } = useAddImage({
|
||||||
|
size: 'small',
|
||||||
|
handleUpdateImgList: handleUpdateImgList,
|
||||||
|
updateUidCount: () => `img-${Date.now()}`
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{ImageURLInput}
|
||||||
{actions.length > 1 && !loading ? (
|
{actions.length > 1 && !loading ? (
|
||||||
<div className="actions">
|
<div
|
||||||
|
className={classNames('actions', {
|
||||||
|
'has-url-input': isFromUrl
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="actions-wrap gap-5">
|
<div className="actions-wrap gap-5">
|
||||||
{actions.includes('upload') && data.role === Roles.User && (
|
{actions.includes('upload') &&
|
||||||
<UploadImg handleUpdateImgList={handleUpdateImgList} />
|
data.role === Roles.User &&
|
||||||
)}
|
UploadImageButton}
|
||||||
{actions.includes('upload') && data.role === Roles.User && (
|
{actions.includes('upload') && data.role === Roles.User && (
|
||||||
<UploadAudio
|
<UploadAudio
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -16,10 +16,8 @@ import ThumbImg from '../thumb-img';
|
|||||||
import ThinkContent from './think-content';
|
import ThinkContent from './think-content';
|
||||||
|
|
||||||
const AudioWrapper = styled.div`
|
const AudioWrapper = styled.div`
|
||||||
padding-top: 10px;
|
|
||||||
height: max-content;
|
height: max-content;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
margin-inline: 10px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ThumbImgWrapper = styled.div.attrs({
|
const ThumbImgWrapper = styled.div.attrs({
|
||||||
@@ -29,6 +27,12 @@ const ThumbImgWrapper = styled.div.attrs({
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
padding: 0px;
|
||||||
|
gap: 8px;
|
||||||
|
&.has-content {
|
||||||
|
padding-inline: 8px;
|
||||||
|
padding-block: 8px 0;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface MessageBodyProps {
|
interface MessageBodyProps {
|
||||||
@@ -179,31 +183,6 @@ const MessageBody: React.FC<MessageBodyProps> = forwardRef(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteLastImage = useCallback(() => {
|
|
||||||
if (data.imgs && data.imgs?.length > 0) {
|
|
||||||
const newImgList = [...(data.imgs || [])];
|
|
||||||
const lastImage = newImgList.pop();
|
|
||||||
if (lastImage) {
|
|
||||||
handleDeleteImg(lastImage.uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [data.imgs, handleDeleteImg]);
|
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
|
||||||
(event: any) => {
|
|
||||||
if (
|
|
||||||
event.key === 'Backspace' &&
|
|
||||||
data.content === '' &&
|
|
||||||
data.imgs &&
|
|
||||||
data.imgs?.length > 0
|
|
||||||
) {
|
|
||||||
// inputref blur
|
|
||||||
event.preventDefault();
|
|
||||||
handleDeleteLastImage();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[data, handleDeleteLastImage]
|
|
||||||
);
|
|
||||||
const handleClickWrapper = (e: any) => {
|
const handleClickWrapper = (e: any) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -218,7 +197,7 @@ const MessageBody: React.FC<MessageBodyProps> = forwardRef(
|
|||||||
data.imgs?.length || (data.audio && data.audio?.length > 0)
|
data.imgs?.length || (data.audio && data.audio?.length > 0)
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className="justify-start ">
|
<div className="justify-start">
|
||||||
<ThumbImg
|
<ThumbImg
|
||||||
editable={editable}
|
editable={editable}
|
||||||
dataList={data.imgs || []}
|
dataList={data.imgs || []}
|
||||||
@@ -249,7 +228,12 @@ const MessageBody: React.FC<MessageBodyProps> = forwardRef(
|
|||||||
})}
|
})}
|
||||||
onClick={handleClickWrapper}
|
onClick={handleClickWrapper}
|
||||||
>
|
>
|
||||||
<ThumbImgWrapper>
|
<ThumbImgWrapper
|
||||||
|
className={classNames({
|
||||||
|
'has-content':
|
||||||
|
data.imgs?.length || (data.audio && data.audio?.length > 0)
|
||||||
|
})}
|
||||||
|
>
|
||||||
<ThumbImg
|
<ThumbImg
|
||||||
style={{ paddingBlockEnd: 0 }}
|
style={{ paddingBlockEnd: 0 }}
|
||||||
editable={editable}
|
editable={editable}
|
||||||
@@ -257,7 +241,7 @@ const MessageBody: React.FC<MessageBodyProps> = forwardRef(
|
|||||||
onDelete={handleDeleteImg}
|
onDelete={handleDeleteImg}
|
||||||
/>
|
/>
|
||||||
{data.audio && data.audio.length > 0 && (
|
{data.audio && data.audio.length > 0 && (
|
||||||
<AudioWrapper className={data.imgs?.length ? '' : 'm-l-10'}>
|
<AudioWrapper>
|
||||||
<SimpleAudio
|
<SimpleAudio
|
||||||
url={data.audio?.[0]?.data.url}
|
url={data.audio?.[0]?.data.url}
|
||||||
name={data.audio?.[0]?.data.name}
|
name={data.audio?.[0]?.data.name}
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import DropDownActions from '@/components/drop-down-actions';
|
||||||
|
import {
|
||||||
|
LinkOutlined,
|
||||||
|
PictureOutlined,
|
||||||
|
UploadOutlined
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
|
import { Button, Input, Tooltip } from 'antd';
|
||||||
|
import { useRef, useState } from 'react';
|
||||||
|
import UploadImg from '../components/upload-img';
|
||||||
|
|
||||||
|
const regex = /(https?:\/\/\S+\.(png|jpg|jpeg))/gi;
|
||||||
|
|
||||||
|
const useAddImage = (options: {
|
||||||
|
size?: 'small' | 'middle' | 'large';
|
||||||
|
handleUpdateImgList: (
|
||||||
|
list: { uid: number | string; dataUrl: string }[]
|
||||||
|
) => void;
|
||||||
|
updateUidCount: () => number | string;
|
||||||
|
}) => {
|
||||||
|
const { handleUpdateImgList, updateUidCount, size = 'middle' } = options;
|
||||||
|
const intl = useIntl();
|
||||||
|
const [isFromUrl, setIsFromUrl] = useState(false);
|
||||||
|
const [openImgTips, setOpenImgTips] = useState(false);
|
||||||
|
const inputImgRef = useRef<any>(null);
|
||||||
|
|
||||||
|
const handleAddImgFromUrl = () => {
|
||||||
|
setIsFromUrl(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
inputImgRef.current?.focus?.();
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputImageUrl = (e: any) => {
|
||||||
|
const url = e.target.value;
|
||||||
|
const isValid = regex.test(url);
|
||||||
|
setOpenImgTips(!isValid);
|
||||||
|
if (isValid) {
|
||||||
|
handleUpdateImgList([
|
||||||
|
{
|
||||||
|
uid: updateUidCount(),
|
||||||
|
dataUrl: e.target.value
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
setIsFromUrl(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setOpenImgTips(false);
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOnEscape = (e: any) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
setIsFromUrl(false);
|
||||||
|
setOpenImgTips(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ImageURLInput = isFromUrl ? (
|
||||||
|
<Tooltip
|
||||||
|
open={openImgTips}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
id: 'playground.uploadImage.url.invalid'
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
ref={inputImgRef}
|
||||||
|
status={openImgTips ? 'error' : ''}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'playground.uploadImage.url.holder'
|
||||||
|
})}
|
||||||
|
style={{ width: 360, height: 32 }}
|
||||||
|
onBlur={handleInputImageUrl}
|
||||||
|
onPressEnter={handleInputImageUrl}
|
||||||
|
onKeyDown={handleOnEscape}
|
||||||
|
></Input>
|
||||||
|
</Tooltip>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
const UploadImageButton = (
|
||||||
|
<DropDownActions
|
||||||
|
placement={'topLeft'}
|
||||||
|
menu={{
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<UploadImg
|
||||||
|
handleUpdateImgList={handleUpdateImgList}
|
||||||
|
size="middle"
|
||||||
|
>
|
||||||
|
{intl.formatMessage({ id: 'playground.img.upload' })}
|
||||||
|
</UploadImg>
|
||||||
|
),
|
||||||
|
key: 'upload_image',
|
||||||
|
icon: <UploadOutlined />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: intl.formatMessage({
|
||||||
|
id: 'playground.uploadImage.url.button'
|
||||||
|
}),
|
||||||
|
key: 'add_image_url',
|
||||||
|
icon: <LinkOutlined />,
|
||||||
|
onClick: handleAddImgFromUrl
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="text" size={size} icon={<PictureOutlined />}></Button>
|
||||||
|
</DropDownActions>
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isFromUrl,
|
||||||
|
ImageURLInput,
|
||||||
|
UploadImageButton
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useAddImage;
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
const imageUrlRegex = /(https?:\/\/\S+\.(png|jpg|jpeg))/gi;
|
|
||||||
|
|
||||||
const useFindImages = () => {
|
|
||||||
function extractImageUrls(text: string) {
|
|
||||||
return [...text.matchAll(imageUrlRegex)].map((m) => m[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [pendingUrls, setPendingUrls] = useState<string[]>([]);
|
|
||||||
|
|
||||||
const debounceExtract = _.debounce((text: string) => {
|
|
||||||
const urls = extractImageUrls(text);
|
|
||||||
if (urls.length > 0) {
|
|
||||||
setPendingUrls(urls);
|
|
||||||
} else {
|
|
||||||
setPendingUrls([]);
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
|
|
||||||
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
const text = e.target.value;
|
|
||||||
debounceExtract(text);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleInputChange
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -156,7 +156,7 @@ const Playground: React.FC = () => {
|
|||||||
activeKey={activeKey}
|
activeKey={activeKey}
|
||||||
key="view-code-buttons"
|
key="view-code-buttons"
|
||||||
/>,
|
/>,
|
||||||
<>
|
<div key="divider-wrapper">
|
||||||
{activeKey === 'chat' && (
|
{activeKey === 'chat' && (
|
||||||
<Divider
|
<Divider
|
||||||
key="divider"
|
key="divider"
|
||||||
@@ -164,7 +164,7 @@ const Playground: React.FC = () => {
|
|||||||
style={{ height: 24, marginInline: 16 }}
|
style={{ height: 24, marginInline: 16 }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>,
|
</div>,
|
||||||
<ExtraContent key="extra-content" />
|
<ExtraContent key="extra-content" />
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -33,6 +33,11 @@
|
|||||||
border: 1px solid var(--ant-color-border);
|
border: 1px solid var(--ant-color-border);
|
||||||
border-radius: var(--border-radius-base);
|
border-radius: var(--border-radius-base);
|
||||||
background-color: var(--color-white-1);
|
background-color: var(--color-white-1);
|
||||||
|
|
||||||
|
&.has-url-input {
|
||||||
|
display: flex;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions-wrap {
|
.actions-wrap {
|
||||||
|
|||||||
@@ -58,5 +58,4 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: 10px;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,50 +28,43 @@ export const loadAudioData = async (
|
|||||||
url: string;
|
url: string;
|
||||||
}> => {
|
}> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
const audioBlob = new Blob([data], { type: type });
|
||||||
const audioBlob = new Blob([data], { type: type });
|
const fileSize = convertFileSize(audioBlob.size);
|
||||||
const fileSize = convertFileSize(audioBlob.size);
|
|
||||||
|
|
||||||
const audio = document.createElement('audio');
|
const audio = document.createElement('audio');
|
||||||
const url = URL.createObjectURL(audioBlob);
|
const url = URL.createObjectURL(audioBlob);
|
||||||
audio.src = url;
|
audio.src = url;
|
||||||
|
|
||||||
audio.addEventListener('loadedmetadata', () => {
|
audio.addEventListener('loadedmetadata', () => {
|
||||||
const duration = audio.duration;
|
const duration = audio.duration;
|
||||||
resolve({
|
resolve({
|
||||||
data: audioBlob,
|
data: audioBlob,
|
||||||
size: fileSize,
|
size: fileSize,
|
||||||
type: type,
|
type: type,
|
||||||
duration: Math.ceil(duration),
|
duration: Math.ceil(duration),
|
||||||
url: url
|
url: url
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
audio.addEventListener('ended', () => {
|
audio.addEventListener('ended', () => {
|
||||||
URL.revokeObjectURL(audio.src);
|
URL.revokeObjectURL(audio.src);
|
||||||
});
|
});
|
||||||
|
|
||||||
audio.addEventListener('error', () => {
|
audio.addEventListener('error', () => {
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
message.error('Failed to load audio metadata invalid file');
|
message.error('Failed to load audio metadata invalid file');
|
||||||
reject(new Error('Failed to load audio metadata invalid file'));
|
reject(new Error('Failed to load audio metadata invalid file'));
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
console.log('error====', error);
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const readAudioFile = async (
|
export const readAudioFile = async (
|
||||||
file: File
|
file: File
|
||||||
): Promise<{ url: string; name: string; duration: number }> => {
|
): Promise<{ url: string; name: string; duration: number }> => {
|
||||||
console.log('file====', file);
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = async function (e: any) {
|
reader.onload = async function (e: any) {
|
||||||
try {
|
try {
|
||||||
console.log('file====', file);
|
|
||||||
const arrayBuffer = e.target.result;
|
const arrayBuffer = e.target.result;
|
||||||
const audioData = await loadAudioData(arrayBuffer, file.type);
|
const audioData = await loadAudioData(arrayBuffer, file.type);
|
||||||
resolve({
|
resolve({
|
||||||
@@ -79,7 +72,6 @@ export const readAudioFile = async (
|
|||||||
name: file.name
|
name: file.name
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('error====', error);
|
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user