/* * Copyright 2025 coze-dev Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* eslint-disable complexity */ /* eslint-disable @typescript-eslint/no-magic-numbers */ /* eslint-disable react-hooks/rules-of-hooks */ import { useMemo, useRef, useState, type FC } from 'react'; import classNames from 'classnames'; import { useMount } from 'ahooks'; import { CommonE2e } from '@coze-data/e2e'; import { I18n } from '@coze-arch/i18n'; import { IconCozEdit } from '@coze-arch/coze-design/icons'; import { type FileItem, type UploadProps } from '@coze-arch/bot-semi/Upload'; import { type CommonFieldProps } from '@coze-arch/bot-semi/Form'; import { UIButton, Toast, withField, Image, Upload } from '@coze-arch/bot-semi'; import { IconAvatarEditMask } from '@coze-arch/bot-icons'; import { type FileBizType, IconType } from '@coze-arch/bot-api/developer_api'; import { DeveloperApi } from '@coze-arch/bot-api'; import customUploadRequest from './utils/custom-upload-request'; import s from './index.module.less'; export type UploadValue = { uid: string | undefined; url: string }[]; export interface GenerateInfo { name: string; desc?: string; } export interface RenderAutoGenerateParams { uploadPicture: () => void; showAiAvatar: boolean; setShowAiAvatar: (show: boolean) => void; generateInfo?: GenerateInfo | (() => GenerateInfo); generateTooltip?: { generateBtnText?: string; contentNotLegalText?: string; }; onChange?: (value: UploadValue) => void; maxCandidateCount?: number; } interface PackageUploadProps { value?: FileItem[]; onChange?: (value: UploadValue) => void; fileBizType: FileBizType; uploadButtonText?: string; iconType?: IconType; disabled?: boolean; avatarClassName?: string; uploadClassName?: string; triggerClassName?: string; maskIcon?: React.ReactNode; /** * Edit the display mode of the mask * - full-center (default): overall cover black transparent mask, Icon centered show.hover display * - right-bottom: lower right masking, long display */ maskMode?: 'full-center' | 'right-bottom'; /** Edit the className of the mask */ editMaskClassName?: string; /** max size */ maxSize?: number; withAutoGenerate?: boolean; generateInfo?: GenerateInfo | (() => GenerateInfo); generateTooltip?: { generateBtnText?: string; contentNotLegalText?: string; }; /** * Maximum number of candidates automatically generated * @default 5 */ maxCandidateCount?: number; beforeUploadCustom?: () => void; afterUploadCustom?: () => void; accept?: string; onGenerateStaticImageClick?: React.MouseEventHandler; onGenerateGifClick?: React.MouseEventHandler; onSizeError?: () => void; // Custom custom generated image logic renderAutoGenerate?: (params: RenderAutoGenerateParams) => React.ReactNode; testId?: string; } // eslint-disable-next-line @coze-arch/max-line-per-function const _PictureUpload = (props: PackageUploadProps) => { // business const { onChange, value, fileBizType, uploadButtonText, iconType = IconType.Bot, disabled = false, avatarClassName, uploadClassName, triggerClassName, maskIcon, maskMode = 'full-center', editMaskClassName, withAutoGenerate = false, generateInfo, generateTooltip, beforeUploadCustom, afterUploadCustom, accept = 'image/*', maxCandidateCount, renderAutoGenerate, onSizeError, maxSize = 2 * 1024, testId, } = props; const uploadRef = useRef(null); const pictureValue = value?.at(0); const [loadingIcon, setLoadingIcon] = useState(!pictureValue); const [showAiAvatar, setShowAiAvatar] = useState(withAutoGenerate); const maskIconInner = useMemo(() => { if (maskIcon) { return maskIcon; } return ( ); }, [maskIcon, maskMode]); const getIcon = async () => { setLoadingIcon(true); try { const res = await DeveloperApi.GetIcon({ icon_type: iconType, }); const iconData = res.data?.icon_list?.[0]; if (!iconData) { Toast.error({ content: I18n.t('error'), showClose: false, }); return; } const { url = '', uri = '' } = iconData; onChange?.([ { url, uid: uri, }, ]); } catch (e) { Toast.error({ content: I18n.t('error'), showClose: false, }); } }; useMount(() => { if (!pictureValue) { getIcon().then(() => setLoadingIcon(false)); } }); const customRequest: UploadProps['customRequest'] = options => { customUploadRequest({ ...options, fileBizType, onSuccess: data => { if (withAutoGenerate) { setShowAiAvatar(false); } options.onSuccess(data); onChange?.([ { uid: data?.upload_uri || '', url: data?.upload_url || '', }, ]); }, beforeUploadCustom, afterUploadCustom, }); }; const uploadPicture = () => { uploadRef.current?.openFileDialog(); }; return (
{ if (onSizeError) { onSizeError(); return; } Toast.error({ // Starling toggle content: I18n.t( 'dataset_upload_image_warning', {}, 'Please upload an image less than 2MB', ), showClose: false, }); }} >
} />
{maskMode === 'right-bottom' && ( )}
{maskIconInner}
{uploadButtonText && !disabled ? (
{uploadButtonText}
) : null} {withAutoGenerate && renderAutoGenerate ? renderAutoGenerate({ uploadPicture, showAiAvatar, setShowAiAvatar, generateInfo, generateTooltip, onChange, maxCandidateCount, }) : null}
); }; export const PictureUpload: FC = withField(_PictureUpload);