feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
.btn {
min-width: 80px;
font-size: 14px;
line-height: 20px;
&.grey {
background: linear-gradient(90deg, rgba(var(--coze-brand-1), var(--coze-brand-1-alpha)) 0%, rgba(var(--coze-purple-1), var(--coze-purple-1-alpha)) 100%) !important;
:global {
.coz-ai-button-icon {
color: rgba(var(--coze-brand-3), var(--coze-brand-3-alpha)) !important;
}
.coz-ai-button-text {
background: linear-gradient(90deg, rgba(var(--coze-brand-3), var(--coze-brand-3-alpha)) 0%, rgba(var(--coze-purple-3), var(--coze-purple-3-alpha)) 100%) !important;
background-clip: text !important;
-webkit-text-fill-color: transparent !important;
}
}
}
.generate-icon {
position: relative;
top: 2px;
margin-right: 8px;
}
:global {
.icon-icon-coz_loading {
position: relative;
top: 1px;
}
}
}

View File

@@ -0,0 +1,146 @@
/*
* 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.
*/
import { type CSSProperties, useState, useEffect } from 'react';
import classNames from 'classnames';
import { I18n } from '@coze-arch/i18n';
import { AIButton, Tooltip } from '@coze-arch/coze-design';
import { PicType } from '@coze-arch/bot-api/playground_api';
import { PlaygroundApi } from '@coze-arch/bot-api';
import s from './index.module.less';
export interface GenerateButtonProps {
scene?: 'gif' | 'static_image';
loading?: boolean;
disabled?: boolean;
size?: 'small' | 'default' | 'large';
text?: string;
cancelText?: string;
tooltipText?: string;
className?: string;
transparent?: boolean;
style?: CSSProperties;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
onCancel?: React.MouseEventHandler<HTMLButtonElement>;
}
export const GenerateButton: React.FC<GenerateButtonProps> = ({
scene,
loading = false,
disabled: outerDisabled = false,
tooltipText: outerTooltipText,
className,
transparent = false,
style,
onCancel,
onClick,
size,
text,
cancelText,
}) => {
const [exceedImageGenCountLimit, setExceedImageGenCountLimit] =
useState(false);
const [hovering, setHovering] = useState(false);
const handleClick = loading ? onCancel : onClick;
const innerLoading = hovering ? false : loading;
const disabled = exceedImageGenCountLimit || outerDisabled;
const exceedLimitTooltipText = {
gif: I18n.t('profilepicture_popup_toast_daymax_gif'),
static_image: I18n.t('profilepicture_popup_toast_daymax_image'),
};
const tooltipText =
exceedImageGenCountLimit && scene
? exceedLimitTooltipText[scene]
: outerTooltipText;
const getGenPicTimes = async () => {
if (!scene) {
return;
}
try {
const { data } = await PlaygroundApi.GetGenPicTimes();
if (data?.infos) {
let gifCount = 0;
let staticImageCount = 0;
data.infos.forEach(({ type, times }) => {
if (
[PicType.IconGif, PicType.BackgroundGif].includes(type as PicType)
) {
gifCount += times || 0;
} else if (
[PicType.IconStatic, PicType.BackgroundStatic].includes(
type as PicType,
)
) {
staticImageCount += times || 0;
}
});
if (
(scene === 'gif' && gifCount >= 10) ||
(scene === 'static_image' && staticImageCount >= 20)
) {
// 达到上限,禁用按钮
setExceedImageGenCountLimit(true);
}
}
// eslint-disable-next-line @coze-arch/no-empty-catch
} catch (error) {
// empty
}
};
useEffect(() => {
// 获取图片限制每天限制10个gif20个静态图根据scene来判断是否达到上限
if (!loading) {
getGenPicTimes();
}
}, [loading]);
const button = (
<AIButton
color="aihglt"
className={classNames(s.btn, {
[s.grey]: disabled || (loading && !hovering),
})}
style={
disabled
? { cursor: 'not-allowed', ...style }
: { cursor: 'pointer', ...style }
}
loading={innerLoading}
size={size}
onClick={disabled ? undefined : handleClick}
>
{hovering && loading
? cancelText || I18n.t('profilepicture_popup_cancel')
: text || I18n.t('profilepicture_popup_generate')}
</AIButton>
);
return (
<div
className={classNames(
className,
'pointer-events-auto inline-block leading-none rounded-lg',
{
'coz-bg-max': !transparent,
},
)}
onMouseEnter={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
>
{tooltipText ? <Tooltip content={tooltipText}>{button}</Tooltip> : button}
</div>
);
};