feat: add image from url
This commit is contained in:
@@ -81,6 +81,7 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
|
|||||||
'clusters.addworker.extraVolume.holder':
|
'clusters.addworker.extraVolume.holder':
|
||||||
'e.g. /data/models (path must start with /)',
|
'e.g. /data/models (path must start with /)',
|
||||||
'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device',
|
'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device',
|
||||||
'clusters.button.genToken': 'Generate Token',
|
'clusters.button.genToken':
|
||||||
|
'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
|
||||||
'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`
|
'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -156,5 +156,10 @@ export default {
|
|||||||
'playground.image.generate.error':
|
'playground.image.generate.error':
|
||||||
'Something went wrong. The image could not be generated.',
|
'Something went wrong. The image could not be generated.',
|
||||||
'playground.uploadfile.sizeError':
|
'playground.uploadfile.sizeError':
|
||||||
'File size exceeds the limit. Maximum allowed: {size}.'
|
'File size exceeds the limit. Maximum allowed: {size}.',
|
||||||
|
'playground.uploadImage.url.invalid':
|
||||||
|
'Please enter a direct .png or .jpg image URL (e.g. https://.../image.png). Press ESC to cancel.',
|
||||||
|
'playground.uploadImage.url.holder':
|
||||||
|
'Enter an image URL (supports .png, .jpeg, .jpg)',
|
||||||
|
'playground.uploadImage.url.button': 'Add Image from URL'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
|
|||||||
'clusters.addworker.extraVolume.holder':
|
'clusters.addworker.extraVolume.holder':
|
||||||
'e.g. /data/models (path must start with /)',
|
'e.g. /data/models (path must start with /)',
|
||||||
'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device',
|
'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device',
|
||||||
'clusters.button.genToken': 'Generate Token',
|
'clusters.button.genToken':
|
||||||
|
'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
|
||||||
'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`
|
'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -154,6 +155,6 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
|
|||||||
// 66. 'clusters.addworker.autoDetect': 'Auto-detect',
|
// 66. 'clusters.addworker.autoDetect': 'Auto-detect',
|
||||||
// 67. 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)'
|
// 67. 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)'
|
||||||
// 68. 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device',
|
// 68. 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device',
|
||||||
// 69. 'clusters.button.genToken': 'Generate Token',
|
// 69. 'clusters.button.genToken': 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
|
||||||
// 70. 'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`
|
// 70. 'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -159,11 +159,19 @@ export default {
|
|||||||
'playground.image.generate.error':
|
'playground.image.generate.error':
|
||||||
'Something went wrong. The image could not be generated.',
|
'Something went wrong. The image could not be generated.',
|
||||||
'playground.uploadfile.sizeError':
|
'playground.uploadfile.sizeError':
|
||||||
'File size exceeds the limit. Maximum allowed: {size}.'
|
'File size exceeds the limit. Maximum allowed: {size}.',
|
||||||
|
'playground.uploadImage.url.invalid':
|
||||||
|
'Please enter a direct .png or .jpg image URL (e.g. https://.../image.png). Press ESC to cancel.',
|
||||||
|
'playground.uploadImage.url.holder':
|
||||||
|
'Enter an image URL (supports .png, .jpeg, .jpg)',
|
||||||
|
'playground.uploadImage.url.button': 'Add Image from URL'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
// 1. 'playground.rerank.query.validate': 'The query is required.'
|
// 1. 'playground.rerank.query.validate': 'The query is required.'
|
||||||
// 2. 'playground.image.generate.error': 'Something went wrong. The image could not be generated.',
|
// 2. 'playground.image.generate.error': 'Something went wrong. The image could not be generated.',
|
||||||
// 3. 'playground.uploadfile.sizeError': 'File size exceeds the limit. Maximum allowed: {size}.'
|
// 3. 'playground.uploadfile.sizeError': 'File size exceeds the limit. Maximum allowed: {size}.'
|
||||||
|
// 4. 'playground.uploadImage.url.invalid': 'Please enter a direct .png or .jpg image URL(e.g. https://.../image.png). Press ESC to cancel.',
|
||||||
|
// 5. 'playground.uploadImage.url.holder': 'Enter an image URL (supports .png, .jpeg, .jpg)'
|
||||||
|
// 6. 'playground.uploadImage.url.button': 'Add Image from URL'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -81,10 +81,12 @@ export default {
|
|||||||
'clusters.addworker.extraVolume.holder':
|
'clusters.addworker.extraVolume.holder':
|
||||||
'напр. /data/models (путь должен начинаться с /)',
|
'напр. /data/models (путь должен начинаться с /)',
|
||||||
'clusters.addworker.vendorNotes.title': 'Примечания для устройств {vendor}',
|
'clusters.addworker.vendorNotes.title': 'Примечания для устройств {vendor}',
|
||||||
'clusters.button.genToken': 'Сгенерировать токен',
|
'clusters.button.genToken':
|
||||||
|
'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
|
||||||
'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`
|
'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
// 1. 'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`
|
// 1. 'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`,
|
||||||
|
// 2. 'clusters.button.genToken': 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
|
||||||
// ================================================================
|
// ================================================================
|
||||||
|
|||||||
@@ -150,10 +150,19 @@ export default {
|
|||||||
'playground.model.noavailable.tips2':
|
'playground.model.noavailable.tips2':
|
||||||
'Если нужная модель не отображается, убедитесь, что она запущена и ей присвоена правильная категория. Если категория указана неверно, её можно изменить вручную в настройках модели.',
|
'Если нужная модель не отображается, убедитесь, что она запущена и ей присвоена правильная категория. Если категория указана неверно, её можно изменить вручную в настройках модели.',
|
||||||
'playground.rerank.query.validate': 'Необходимо указать запрос.',
|
'playground.rerank.query.validate': 'Необходимо указать запрос.',
|
||||||
'playground.image.generate.error': 'Произошла ошибка. Не удалось сгенерировать изображение.',
|
'playground.image.generate.error':
|
||||||
'playground.uploadfile.sizeError': 'Размер файла превышает ограничение. Максимальный размер: {size}.'
|
'Произошла ошибка. Не удалось сгенерировать изображение.',
|
||||||
|
'playground.uploadfile.sizeError':
|
||||||
|
'Размер файла превышает ограничение. Максимальный размер: {size}.',
|
||||||
|
'playground.uploadImage.url.invalid':
|
||||||
|
'Please enter a direct .png or .jpg image URL (e.g. https://.../image.png). Press ESC to cancel.',
|
||||||
|
'playground.uploadImage.url.holder':
|
||||||
|
'Enter an image URL (supports .png, .jpeg, .jpg)',
|
||||||
|
'playground.uploadImage.url.button': 'Add Image from URL'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
// 1. 'playground.uploadImage.url.invalid': 'Please enter a direct .png or .jpg image URL(e.g. https://.../image.png). Press ESC to cancel.',
|
||||||
|
// 2. 'playground.uploadImage.url.holder': 'Enter an image URL (supports .png, .jpeg, .jpg)',
|
||||||
|
// 3. 'playground.uploadImage.url.button': 'Add Image from URL'
|
||||||
// ========== End of To-Do List ==========
|
// ========== End of To-Do List ==========
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ export default {
|
|||||||
'clusters.addworker.extraVolume.holder':
|
'clusters.addworker.extraVolume.holder':
|
||||||
'例如:/data/models(路径需以 / 开头)',
|
'例如:/data/models(路径需以 / 开头)',
|
||||||
'clusters.addworker.vendorNotes.title': '{vendor}设备注意事项',
|
'clusters.addworker.vendorNotes.title': '{vendor}设备注意事项',
|
||||||
'clusters.button.genToken': '生成令牌',
|
'clusters.button.genToken':
|
||||||
|
'需要创建令牌?点击<a href="{link}" target="_blank">这里</a>。',
|
||||||
'clusters.addworker.amdNotes-01':
|
'clusters.addworker.amdNotes-01':
|
||||||
'如果 <span class="bold-text">/opt/rocm</span> 目录不存在,请创建一个指向已安装 ROCm 路径的符号链接:<span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>。'
|
'如果 <span class="bold-text">/opt/rocm</span> 目录不存在,请创建一个指向已安装 ROCm 路径的符号链接:<span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>。'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -148,5 +148,11 @@ export default {
|
|||||||
'若预期的模型未显示,请检查模型是否已正常运行并被正确分类。如分类不正确,请编辑模型并手动调整其类别。',
|
'若预期的模型未显示,请检查模型是否已正常运行并被正确分类。如分类不正确,请编辑模型并手动调整其类别。',
|
||||||
'playground.rerank.query.validate': '查询内容不能为空',
|
'playground.rerank.query.validate': '查询内容不能为空',
|
||||||
'playground.image.generate.error': '出了一点问题,图片未能生成。',
|
'playground.image.generate.error': '出了一点问题,图片未能生成。',
|
||||||
'playground.uploadfile.sizeError': '上传的文件大小超过限制,最大允许 {size}。'
|
'playground.uploadfile.sizeError':
|
||||||
|
'上传的文件大小超过限制,最大允许 {size}。',
|
||||||
|
'playground.uploadImage.url.invalid':
|
||||||
|
'请输入直接的 .png 或 .jpg 图片链接(例如:https://…/image.png)。按 ESC 可取消。',
|
||||||
|
'playground.uploadImage.url.holder':
|
||||||
|
'请输入图片链接(支持 .png、.jpeg、.jpg 格式)',
|
||||||
|
'playground.uploadImage.url.button': '从链接添加图片'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ export const yamlTemplate = `# ----------------------------------------
|
|||||||
# - must be endwith '-custom'
|
# - must be endwith '-custom'
|
||||||
# version_configs:
|
# version_configs:
|
||||||
# - image_name: required
|
# - image_name: required
|
||||||
# - run_command: required
|
# - run_command: optional
|
||||||
# - custom_framework:
|
# - custom_framework:
|
||||||
# - optional
|
# - optional
|
||||||
# - choose from: ${Object.values(GPUDriverMap).join(', ')}, CPU
|
# - choose from: ${Object.values(GPUDriverMap).join(', ')}, CPU
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ import SealInput from '@/components/seal-form/seal-input';
|
|||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
import useAppUtils from '@/hooks/use-app-utils';
|
import useAppUtils from '@/hooks/use-app-utils';
|
||||||
import { ExportOutlined } from '@ant-design/icons';
|
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form, Typography } from 'antd';
|
import { Form } from 'antd';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { ProviderType, ProviderValueMap } from '../config';
|
import { ProviderType, ProviderValueMap } from '../config';
|
||||||
@@ -117,17 +116,21 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
id: 'clusters.credential.token'
|
id: 'clusters.credential.token'
|
||||||
})}
|
})}
|
||||||
required={action === PageAction.CREATE}
|
required={action === PageAction.CREATE}
|
||||||
|
description={
|
||||||
|
<span
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: intl.formatMessage(
|
||||||
|
{
|
||||||
|
id: 'clusters.button.genToken'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
link: 'https://cloud.digitalocean.com/account/api/tokens'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
></span>
|
||||||
|
}
|
||||||
></SealInput.Password>
|
></SealInput.Password>
|
||||||
<ExtraContent>
|
|
||||||
<Typography.Link
|
|
||||||
href="https://cloud.digitalocean.com/account/api/tokens"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{intl.formatMessage({ id: 'clusters.button.genToken' })}{' '}
|
|
||||||
<ExportOutlined style={{ transform: 'scale(0.8)' }} />
|
|
||||||
</Typography.Link>
|
|
||||||
</ExtraContent>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
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';
|
||||||
@@ -10,8 +11,11 @@ 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';
|
||||||
@@ -163,8 +167,11 @@ const MessageInput: React.FC<MessageInputProps> = forwardRef(
|
|||||||
content: '',
|
content: '',
|
||||||
imgs: []
|
imgs: []
|
||||||
});
|
});
|
||||||
|
const [isFromUrl, setIsFromUrl] = useState(false);
|
||||||
|
const [openImgTips, setOpenImgTips] = useState(false);
|
||||||
const uidCountRef = useRef(0);
|
const uidCountRef = useRef(0);
|
||||||
const inputRef = useRef<any>(null);
|
const inputRef = useRef<any>(null);
|
||||||
|
const inputImgRef = useRef<any>(null);
|
||||||
|
|
||||||
const updateUidCount = () => {
|
const updateUidCount = () => {
|
||||||
uidCountRef.current += 1;
|
uidCountRef.current += 1;
|
||||||
@@ -293,6 +300,39 @@ 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[];
|
||||||
@@ -412,11 +452,41 @@ const MessageInput: React.FC<MessageInputProps> = forwardRef(
|
|||||||
</Checkbox>
|
</Checkbox>
|
||||||
)}
|
)}
|
||||||
{actions.includes('upload') && message.role === Roles.User && (
|
{actions.includes('upload') && message.role === Roles.User && (
|
||||||
<UploadImg
|
<DropDownActions
|
||||||
handleUpdateImgList={handleUpdateImgList}
|
placement={'topLeft'}
|
||||||
size="middle"
|
menu={{
|
||||||
></UploadImg>
|
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
|
||||||
maxFileSize={1024 * 1024}
|
maxFileSize={1024 * 1024}
|
||||||
@@ -533,6 +603,26 @@ 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
|
||||||
|
|||||||
@@ -184,4 +184,4 @@ const UploadImg: React.FC<UploadImgProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(UploadImg);
|
export default UploadImg;
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user