feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
.ctn {
|
||||
cursor: pointer;
|
||||
|
||||
position: absolute;
|
||||
right: -1px;
|
||||
bottom: -1px;
|
||||
|
||||
box-sizing: content-box;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
|
||||
font-size: 12px;
|
||||
|
||||
background-color: var(--coz-fg-white, #fff);
|
||||
border: 1.5px solid var(--coz-fg-white, #fff);
|
||||
border-radius: 50%;
|
||||
|
||||
&.loading {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
padding: 2px;
|
||||
|
||||
font-size: 8px;
|
||||
|
||||
background-color: var(--coz-mg-hglt-plus-green, #00B83E);
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
|
||||
&.icon-generating {
|
||||
color: var(--coz-fg-white, #fff);
|
||||
}
|
||||
|
||||
&.icon-success {
|
||||
color: var(--coz-mg-color-plus-emerald, #00B83E);
|
||||
}
|
||||
|
||||
&.icon-fail {
|
||||
color: var(--coz-mg-color-plus-orange, #FF811A);
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.icon-icon-coz_loading.icon-icon-loading {
|
||||
animation: semi-animation-rotate .6s linear infinite;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 classNames from 'classnames';
|
||||
import { DotStatus } from '@coze-studio/bot-detail-store';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
IconCozCheckMarkCircleFillPalette,
|
||||
IconCozLoading,
|
||||
IconCozWarningCircleFillPalette,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface AvatarBackgroundNoticeDotProps {
|
||||
status: DotStatus;
|
||||
}
|
||||
|
||||
export const AvatarBackgroundNoticeDot: React.FC<
|
||||
AvatarBackgroundNoticeDotProps
|
||||
> = ({ status }) => {
|
||||
if (status === DotStatus.None || status === DotStatus.Cancel) {
|
||||
return null;
|
||||
}
|
||||
const dot = {
|
||||
[DotStatus.Generating]: (
|
||||
<Tooltip content={I18n.t('profilepicture_hover_generating')}>
|
||||
<IconCozLoading
|
||||
className={classNames(s.icon, s['icon-generating'])}
|
||||
spin={true}
|
||||
/>
|
||||
</Tooltip>
|
||||
),
|
||||
[DotStatus.Success]: (
|
||||
<Tooltip content={I18n.t('profilepicture_hover_generated')}>
|
||||
<IconCozCheckMarkCircleFillPalette
|
||||
className={classNames(s.icon, s['icon-success'])}
|
||||
/>
|
||||
</Tooltip>
|
||||
),
|
||||
[DotStatus.Fail]: (
|
||||
<Tooltip content={I18n.t('profilepicture_hover_failed')}>
|
||||
<IconCozWarningCircleFillPalette
|
||||
className={classNames(s.icon, s['icon-fail'])}
|
||||
/>
|
||||
</Tooltip>
|
||||
),
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
s.ctn,
|
||||
status === DotStatus.Generating ? s.loading : undefined,
|
||||
)}
|
||||
>
|
||||
{dot[status]}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
.container {
|
||||
flex-shrink: 0;
|
||||
max-width: 100%;
|
||||
height: 18px;
|
||||
|
||||
.avatar {
|
||||
overflow: hidden;
|
||||
border-radius: 0;
|
||||
border-radius: 12px;
|
||||
|
||||
img {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
.label-icon {
|
||||
cursor: pointer;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
|
||||
.txt {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
|
||||
@apply coz-fg-dim;
|
||||
|
||||
&.name {
|
||||
@apply coz-fg-secondary;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
&.light {
|
||||
.txt {
|
||||
color: rgba(255, 255, 255, 39%);
|
||||
|
||||
&.name {
|
||||
color: rgba(255, 255, 255, 79%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.white {
|
||||
.txt {
|
||||
color: #FFF;
|
||||
|
||||
&.name {
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.middle {
|
||||
height: 20px;
|
||||
|
||||
.txt {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
&.username {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
height: 20px;
|
||||
|
||||
.label-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.txt {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
117
frontend/packages/studio/components/src/avatar-name/index.tsx
Normal file
117
frontend/packages/studio/components/src/avatar-name/index.tsx
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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 classNames from 'classnames';
|
||||
import { Space, Typography, Tooltip } from '@coze-arch/coze-design';
|
||||
import { Image } from '@coze-arch/bot-semi';
|
||||
|
||||
import AvatarDefault from '../../assets/avatar_default.png';
|
||||
|
||||
import s from './index.module.less';
|
||||
const { Text } = Typography;
|
||||
interface AvatarNameProps {
|
||||
avatar?: string;
|
||||
username?: string;
|
||||
name?: string;
|
||||
label?: {
|
||||
name?: string;
|
||||
icon?: string;
|
||||
href?: string;
|
||||
};
|
||||
theme?: 'default' | 'light' | 'white';
|
||||
className?: string;
|
||||
nameMaxWidth?: number;
|
||||
size?: 'default' | 'large' | 'small';
|
||||
renderCenterSlot?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const AvatarSizeMap = {
|
||||
small: 12,
|
||||
default: 14,
|
||||
large: 16,
|
||||
};
|
||||
|
||||
export const AvatarName = ({
|
||||
avatar,
|
||||
username,
|
||||
name,
|
||||
label,
|
||||
theme,
|
||||
className,
|
||||
nameMaxWidth,
|
||||
size = 'default',
|
||||
renderCenterSlot = null,
|
||||
}: AvatarNameProps) => (
|
||||
<Space
|
||||
spacing={4}
|
||||
className={classNames(
|
||||
s.container,
|
||||
theme && s[theme],
|
||||
{ [s.large]: size === 'large' },
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
width={AvatarSizeMap[size]}
|
||||
height={AvatarSizeMap[size]}
|
||||
src={avatar || AvatarDefault}
|
||||
fallback={<img src={AvatarDefault} width={'100%'} height={'100%'} />}
|
||||
preview={false}
|
||||
className={s.avatar}
|
||||
/>
|
||||
<Space spacing={2}>
|
||||
<Text
|
||||
className={classNames(s.txt, s.name)}
|
||||
ellipsis={{ showTooltip: false, rows: 1 }}
|
||||
style={
|
||||
typeof nameMaxWidth === 'number' ? { maxWidth: nameMaxWidth } : {}
|
||||
}
|
||||
>
|
||||
{name}
|
||||
</Text>
|
||||
{label?.icon ? (
|
||||
<Tooltip
|
||||
showArrow
|
||||
content={label?.name}
|
||||
position={'top'}
|
||||
trigger={label?.name ? 'hover' : 'custom'}
|
||||
>
|
||||
<img
|
||||
src={label?.icon}
|
||||
className={s['label-icon']}
|
||||
tabIndex={-1}
|
||||
onMouseDown={event => {
|
||||
if (label?.href) {
|
||||
event?.preventDefault();
|
||||
event?.stopPropagation();
|
||||
window.open(label.href, '_blank');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</Space>
|
||||
{renderCenterSlot}
|
||||
{username ? (
|
||||
<Text
|
||||
className={classNames(s.txt, s.username)}
|
||||
ellipsis={{ showTooltip: false, rows: 1 }}
|
||||
>
|
||||
@{username}
|
||||
</Text>
|
||||
) : null}
|
||||
</Space>
|
||||
);
|
||||
@@ -0,0 +1,30 @@
|
||||
.popover-content {
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.popover-card-title {
|
||||
margin-bottom: 24px;
|
||||
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
color: #1C1D24;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.popover-card-img {
|
||||
max-width: 360px;
|
||||
border: 1px solid #E5E5E5;
|
||||
border-radius: 8px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.popover-card-icon {
|
||||
color: rgba(29, 28, 35, 35%)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 PropsWithChildren, useRef, useCallback } from 'react';
|
||||
|
||||
import { get } from 'lodash-es';
|
||||
import cls from 'classnames';
|
||||
import { type PopoverProps } from '@coze-arch/bot-semi/Popover';
|
||||
import { type ImageProps } from '@coze-arch/bot-semi/Image';
|
||||
import { Popover, Image } from '@coze-arch/bot-semi';
|
||||
import { IconGroupCardOutlined } from '@coze-arch/bot-icons';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface CardThumbnailPopoverProps extends PopoverProps {
|
||||
title?: string;
|
||||
url?: string;
|
||||
className?: string;
|
||||
imgProps?: ImageProps;
|
||||
}
|
||||
|
||||
export const CardThumbnailPopover: React.FC<
|
||||
PropsWithChildren<CardThumbnailPopoverProps>
|
||||
> = ({ children, url, title = '卡片预览', className, imgProps, ...props }) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const popoverRef = useRef<any>();
|
||||
|
||||
const onImageLoad = useCallback(() => {
|
||||
const calcPosition = get(
|
||||
popoverRef.current,
|
||||
'tooltipRef.current.foundation.calcPosition',
|
||||
);
|
||||
if (typeof calcPosition === 'function') {
|
||||
calcPosition?.();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
position="top"
|
||||
showArrow
|
||||
ref={popoverRef}
|
||||
content={
|
||||
<div className={s['popover-content']}>
|
||||
<div className={s['popover-card-title']}>{title}</div>
|
||||
{url && (
|
||||
<div className={s['popover-card-img']}>
|
||||
<Image src={url} {...imgProps} onLoad={onImageLoad} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
{children || (
|
||||
<IconGroupCardOutlined
|
||||
className={cls(className, s['popover-card-icon'])}
|
||||
/>
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
.carousel-item {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import cls from 'classnames';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface CarouselItemProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const CarouselItem: React.FC<CarouselItemProps> = props => {
|
||||
const { children, className } = props;
|
||||
return (
|
||||
<div className={cls(styles['carousel-item'], className, 'carousel-item')}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
.carousel {
|
||||
position: relative;
|
||||
|
||||
.arrow-container {
|
||||
&::before {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
|
||||
width: 52px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.left {
|
||||
&::before {
|
||||
left: 0;
|
||||
background: linear-gradient(90deg,
|
||||
var(--coz-bg-max) 7.46%,
|
||||
rgba(255, 255, 255, 0%) 100%);
|
||||
}
|
||||
}
|
||||
|
||||
&.right {
|
||||
&::before {
|
||||
right: 0;
|
||||
background: linear-gradient(270deg,
|
||||
var(--coz-bg-max) 7.46%,
|
||||
rgba(255, 255, 255, 0%) 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arrow {
|
||||
cursor: pointer;
|
||||
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
font-size: 20px;
|
||||
|
||||
background: var(--coz-mg-primary);
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 16px 0 #0000001a;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&.no-border {
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.left-arrow {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.right-arrow {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&-content {
|
||||
overflow: auto;
|
||||
flex: 1 0 auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
243
frontend/packages/studio/components/src/carousel/index.tsx
Normal file
243
frontend/packages/studio/components/src/carousel/index.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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 React, { useState, useEffect, useRef } from 'react';
|
||||
|
||||
import { chunk } from 'lodash-es';
|
||||
import cls from 'classnames';
|
||||
import {
|
||||
IconCozArrowRight,
|
||||
IconCozArrowLeft,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { CarouselItem } from './carousel-item';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface CarouselProps {
|
||||
/** 元素布局行数默认为1 */
|
||||
rows?: number;
|
||||
/** 元素布局列数,默认为均分数组 */
|
||||
column?: number;
|
||||
/** 每次点击箭头滚动的百分比,0~1. 默认值为0.5 */
|
||||
scrollStep?: number;
|
||||
/** 滚动回调 */
|
||||
onScroll?: () => void;
|
||||
/** 箭头是否显示边框 */
|
||||
enableArrowBorder?: boolean;
|
||||
/** 箭头是否显示阴影渐变 */
|
||||
enableArrowShalldow?: boolean;
|
||||
/** 子元素样式 */
|
||||
itemClassName?: string;
|
||||
/** 左箭头样式 */
|
||||
leftArrowClassName?: string;
|
||||
/** 右箭头样式 */
|
||||
rightArrowClassName?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface ArrowProps {
|
||||
className?: string;
|
||||
enableArrowBorder?: boolean;
|
||||
enableArrowShalldow?: boolean;
|
||||
onClick: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
const LeftArrow = ({
|
||||
enableArrowBorder,
|
||||
enableArrowShalldow,
|
||||
className,
|
||||
onClick,
|
||||
}: ArrowProps) => (
|
||||
<div
|
||||
className={cls(
|
||||
styles['arrow-container'],
|
||||
{ [styles.left]: enableArrowShalldow },
|
||||
'arrow-container-left',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={cls(
|
||||
className,
|
||||
styles['left-arrow'],
|
||||
styles.arrow,
|
||||
'left-arrow',
|
||||
{
|
||||
[styles['no-border']]: !enableArrowBorder,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<IconCozArrowLeft />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const RightArrow = ({
|
||||
enableArrowBorder,
|
||||
enableArrowShalldow,
|
||||
className,
|
||||
onClick,
|
||||
}: ArrowProps) => (
|
||||
<div
|
||||
className={cls(
|
||||
styles['arrow-container'],
|
||||
{ [styles.right]: enableArrowShalldow },
|
||||
'arrow-container-right',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={cls(
|
||||
className,
|
||||
styles['right-arrow'],
|
||||
styles.arrow,
|
||||
'right-arrow',
|
||||
{
|
||||
[styles['no-border']]: !enableArrowBorder,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<IconCozArrowRight />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Carousel: React.FC<CarouselProps> = ({
|
||||
rows = 1,
|
||||
column,
|
||||
itemClassName = '',
|
||||
leftArrowClassName = '',
|
||||
rightArrowClassName = '',
|
||||
children,
|
||||
enableArrowShalldow = true,
|
||||
scrollStep = 0.5,
|
||||
enableArrowBorder = true,
|
||||
onScroll,
|
||||
}) => {
|
||||
const itemsContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [leftArrowVisible, setLeftArrowVisible] = useState(false);
|
||||
const [rightArrowVisible, setRightArrowVisible] = useState(false);
|
||||
if (!children) {
|
||||
return null;
|
||||
}
|
||||
const carouselItems = React.Children.map(
|
||||
children,
|
||||
(child: React.ReactNode, idx: number) => (
|
||||
<CarouselItem className={itemClassName} key={idx}>
|
||||
{child}
|
||||
</CarouselItem>
|
||||
),
|
||||
);
|
||||
const chunkedCarouselItems: React.ReactNode[][] = chunk(
|
||||
carouselItems,
|
||||
column ?? Math.ceil((carouselItems?.length || 0) / rows),
|
||||
);
|
||||
const rowItems = Array.from(Array(rows).fill(null)).map((_row, idx) => (
|
||||
<div className={cls(styles['carousel-row'], 'carousel-row')} key={idx}>
|
||||
{chunkedCarouselItems[idx]}
|
||||
</div>
|
||||
));
|
||||
const handleScrollLeft = () => {
|
||||
if (
|
||||
itemsContainerRef?.current?.scrollLeft !== undefined &&
|
||||
itemsContainerRef?.current?.clientWidth
|
||||
) {
|
||||
// 部分浏览器不支持 scrollTo 方法
|
||||
itemsContainerRef.current.scrollTo?.({
|
||||
left: Math.max(
|
||||
itemsContainerRef.current.scrollLeft -
|
||||
itemsContainerRef.current.clientWidth * scrollStep,
|
||||
0,
|
||||
),
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
};
|
||||
const handleScrollRight = () => {
|
||||
const containWidth = itemsContainerRef?.current?.clientWidth ?? 0;
|
||||
if (itemsContainerRef?.current?.scrollLeft !== undefined && containWidth) {
|
||||
const scrollLeftMax =
|
||||
(itemsContainerRef?.current?.scrollWidth ?? 0) -
|
||||
(itemsContainerRef?.current?.clientWidth ?? 0);
|
||||
itemsContainerRef.current.scrollTo?.({
|
||||
left: Math.min(
|
||||
itemsContainerRef.current.scrollLeft + containWidth * scrollStep,
|
||||
scrollLeftMax,
|
||||
),
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks -- linter-disable-autofix
|
||||
useEffect(() => {
|
||||
const updateArrowVisible = () => {
|
||||
const scrollLeft = Math.ceil(itemsContainerRef?.current?.scrollLeft ?? 0);
|
||||
const scrollRight =
|
||||
(itemsContainerRef?.current?.scrollWidth ?? 0) -
|
||||
(itemsContainerRef?.current?.clientWidth ?? 0) -
|
||||
scrollLeft;
|
||||
|
||||
const shouldShowArrowLeft = scrollLeft > 0;
|
||||
// 极端场景下存在 1px 偏差
|
||||
const shouldShowArrowRight = Math.abs(scrollRight) > 2;
|
||||
|
||||
setLeftArrowVisible(shouldShowArrowLeft);
|
||||
setRightArrowVisible(shouldShowArrowRight);
|
||||
};
|
||||
const scrollEvent = () => {
|
||||
onScroll?.();
|
||||
updateArrowVisible();
|
||||
};
|
||||
|
||||
// 初始化时判读一次是否显示箭头
|
||||
updateArrowVisible();
|
||||
itemsContainerRef?.current?.addEventListener('scroll', scrollEvent);
|
||||
window?.addEventListener('resize', updateArrowVisible);
|
||||
return () => {
|
||||
itemsContainerRef?.current?.removeEventListener('scroll', scrollEvent);
|
||||
window?.removeEventListener('resize', updateArrowVisible);
|
||||
};
|
||||
}, [children]);
|
||||
|
||||
return (
|
||||
<div className={cls(styles.carousel, 'carousel')}>
|
||||
{leftArrowVisible ? (
|
||||
<LeftArrow
|
||||
onClick={handleScrollLeft}
|
||||
enableArrowBorder={enableArrowBorder}
|
||||
enableArrowShalldow={enableArrowShalldow}
|
||||
className={leftArrowClassName}
|
||||
/>
|
||||
) : null}
|
||||
<div
|
||||
className={cls(styles['carousel-content'], 'carousel-content')}
|
||||
ref={itemsContainerRef}
|
||||
>
|
||||
{rowItems}
|
||||
</div>
|
||||
{rightArrowVisible ? (
|
||||
<RightArrow
|
||||
onClick={handleScrollRight}
|
||||
enableArrowBorder={enableArrowBorder}
|
||||
enableArrowShalldow={enableArrowShalldow}
|
||||
className={rightArrowClassName}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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 RefObject,
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
type SetStateAction,
|
||||
type Dispatch,
|
||||
useState,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
|
||||
import { noop, omit } from 'lodash-es';
|
||||
import { useSize } from 'ahooks';
|
||||
|
||||
interface TConfigItem {
|
||||
width?: number;
|
||||
}
|
||||
|
||||
type ContextItems = Record<symbol, TConfigItem>;
|
||||
|
||||
interface CollapsibleIconButtonContextValue {
|
||||
showText: boolean;
|
||||
setItems: Dispatch<SetStateAction<ContextItems | undefined>>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- 这是 context
|
||||
export const CollapsibleIconButtonContext =
|
||||
createContext<CollapsibleIconButtonContextValue>({
|
||||
showText: true,
|
||||
setItems: noop,
|
||||
});
|
||||
|
||||
export const useItem = (key: symbol, ref: RefObject<HTMLElement>) => {
|
||||
const { showText, setItems } = useContext(CollapsibleIconButtonContext);
|
||||
const size = useSize(ref);
|
||||
useEffect(() => {
|
||||
setItems(items => ({
|
||||
...items,
|
||||
[key]: { width: size?.width ?? 0 },
|
||||
}));
|
||||
}, [size?.width]);
|
||||
|
||||
// 组件销毁后移除
|
||||
useEffect(
|
||||
() => () => {
|
||||
setItems(items => omit(items, key));
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return showText;
|
||||
};
|
||||
|
||||
export const useWrapper = (ref: RefObject<HTMLElement>, gap = 0) => {
|
||||
const [items, setItems] = useState<ContextItems>();
|
||||
const size = useSize(ref);
|
||||
|
||||
const totalWidth = Object.getOwnPropertySymbols(items || {}).reduce<number>(
|
||||
(res, key, index) =>
|
||||
res + (items?.[key]?.width ?? 0) + (index > 0 ? gap : 0),
|
||||
0,
|
||||
);
|
||||
const showText = !!size?.width && size.width >= totalWidth;
|
||||
|
||||
const contextValue = useMemo<CollapsibleIconButtonContextValue>(
|
||||
() => ({
|
||||
showText,
|
||||
setItems,
|
||||
}),
|
||||
[showText],
|
||||
);
|
||||
|
||||
return contextValue;
|
||||
};
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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 PropsWithChildren,
|
||||
type FC,
|
||||
useRef,
|
||||
forwardRef,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
|
||||
import { omit } from 'lodash-es';
|
||||
import {
|
||||
Button,
|
||||
type ButtonProps,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { CollapsibleIconButtonContext, useWrapper, useItem } from './context';
|
||||
|
||||
/** 能让 Group 内的所有 CollapsibleIconButton 根据空余宽度自动展开(露出文案)收起(隐藏文案只剩图标) */
|
||||
export const CollapsibleIconButtonGroup: FC<
|
||||
PropsWithChildren<{
|
||||
/** @default 12 */
|
||||
gap?: number;
|
||||
}>
|
||||
> = ({ children, gap = 12 }) => {
|
||||
const wrapperDomRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const contextValue = useWrapper(wrapperDomRef, gap);
|
||||
return (
|
||||
<div
|
||||
ref={wrapperDomRef}
|
||||
className="flex items-center justify-end flex-1 overflow-hidden"
|
||||
style={{ gap }}
|
||||
>
|
||||
<CollapsibleIconButtonContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</CollapsibleIconButtonContext.Provider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const CollapsibleIconButton = forwardRef<
|
||||
HTMLSpanElement,
|
||||
{
|
||||
itemKey: symbol;
|
||||
text: string;
|
||||
} & ButtonProps
|
||||
>(({ itemKey, text, ...rest }, ref) => {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const showText = useItem(itemKey, contentRef);
|
||||
|
||||
return (
|
||||
<span ref={ref}>
|
||||
{/* 不可见时渲染到屏幕外侧,用于获取宽度 */}
|
||||
<div className={showText ? '' : 'fixed left-[-999px]'} ref={contentRef}>
|
||||
<Button
|
||||
size="default"
|
||||
color="secondary"
|
||||
// 不可见时不附带 testid,避免对 E2E 产生影响
|
||||
{...(showText ? rest : omit(rest, 'data-testid'))}
|
||||
>
|
||||
{text}
|
||||
</Button>
|
||||
</div>
|
||||
{!showText && (
|
||||
<Tooltip content={text}>
|
||||
<IconButton size="default" color="secondary" {...rest} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
/** 更为通用的版本 */
|
||||
export const Collapsible = forwardRef<
|
||||
HTMLSpanElement,
|
||||
{
|
||||
itemKey: symbol;
|
||||
collapsedContent: ReactNode;
|
||||
fullContent: ReactNode;
|
||||
collapsedTooltip?: ReactNode;
|
||||
}
|
||||
>(({ itemKey, fullContent, collapsedContent, collapsedTooltip }, ref) => {
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const showFull = useItem(itemKey, contentRef);
|
||||
|
||||
return (
|
||||
<span ref={ref}>
|
||||
{/* 不可见时渲染到屏幕外侧,用于获取宽度 */}
|
||||
<div className={showFull ? '' : 'fixed left-[-999px]'} ref={contentRef}>
|
||||
{fullContent}
|
||||
</div>
|
||||
{!showFull && (
|
||||
<Tooltip
|
||||
trigger={collapsedTooltip ? 'hover' : 'custom'}
|
||||
content={collapsedTooltip}
|
||||
>
|
||||
<span>{collapsedContent}</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
/** 不会折叠,但参与宽度计算的元素 */
|
||||
export function PlaceholderContainer({
|
||||
itemKey,
|
||||
children,
|
||||
}: PropsWithChildren<{ itemKey: symbol }>) {
|
||||
const ref = useRef<HTMLSpanElement>(null);
|
||||
useItem(itemKey, ref);
|
||||
|
||||
return <span ref={ref}>{children}</span>;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
.coze-brand {
|
||||
display: flex;
|
||||
height: 32px;
|
||||
& > svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
71
frontend/packages/studio/components/src/coze-brand/index.tsx
Normal file
71
frontend/packages/studio/components/src/coze-brand/index.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
IconBrandCnWhiteRow,
|
||||
IconBrandCnBlackRow,
|
||||
IconBrandEnBlackRow,
|
||||
} from '@coze-arch/bot-icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface CozeBrandProps {
|
||||
isOversea: boolean;
|
||||
isWhite?: boolean;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export function CozeBrand({
|
||||
isOversea,
|
||||
isWhite,
|
||||
className,
|
||||
style,
|
||||
}: CozeBrandProps) {
|
||||
const navigate = useNavigate();
|
||||
const navBack = () => {
|
||||
navigate('/');
|
||||
};
|
||||
if (isOversea) {
|
||||
return (
|
||||
<IconBrandEnBlackRow
|
||||
onClick={navBack}
|
||||
className={classNames(styles['coze-brand'], className)}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (isWhite) {
|
||||
return (
|
||||
<IconBrandCnWhiteRow
|
||||
onClick={navBack}
|
||||
className={classNames(styles['coze-brand'], className)}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<IconBrandCnBlackRow
|
||||
onClick={navBack}
|
||||
className={classNames(styles['coze-brand'], className)}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 { HTML5Backend } from 'react-dnd-html5-backend';
|
||||
import { DndProvider as Provider } from 'react-dnd';
|
||||
import { type ReactNode, createContext, useContext } from 'react';
|
||||
const DnDContext = createContext<{
|
||||
isInProvider: boolean;
|
||||
}>({
|
||||
isInProvider: false,
|
||||
});
|
||||
export const DndProvider = ({ children }: { children: ReactNode }) => {
|
||||
const context = useContext(DnDContext);
|
||||
return (
|
||||
<DnDContext.Provider
|
||||
value={{
|
||||
isInProvider: true,
|
||||
}}
|
||||
>
|
||||
{context.isInProvider ? (
|
||||
children
|
||||
) : (
|
||||
<Provider backend={HTML5Backend} context={window}>
|
||||
{children}
|
||||
</Provider>
|
||||
)}
|
||||
</DnDContext.Provider>
|
||||
);
|
||||
};
|
||||
338
frontend/packages/studio/components/src/duplicate-bot/index.tsx
Normal file
338
frontend/packages/studio/components/src/duplicate-bot/index.tsx
Normal file
@@ -0,0 +1,338 @@
|
||||
/*
|
||||
* 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 FC, useState } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { usePageRuntimeStore } from '@coze-studio/bot-detail-store/page-runtime';
|
||||
import { useBotInfoStore } from '@coze-studio/bot-detail-store/bot-info';
|
||||
import {
|
||||
REPORT_EVENTS as ReportEventNames,
|
||||
createReportEvent,
|
||||
} from '@coze-arch/report-events';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Button } from '@coze-arch/coze-design';
|
||||
import { openNewWindow, getParamsFromQuery } from '@coze-arch/bot-utils';
|
||||
import { BotPageFromEnum } from '@coze-arch/bot-typings/common';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { useSpaceList, useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { SpaceApi } from '@coze-arch/bot-space-api';
|
||||
import { type Size } from '@coze-arch/bot-semi/Button';
|
||||
import { UIButton, Toast } from '@coze-arch/bot-semi';
|
||||
import { CustomError } from '@coze-arch/bot-error';
|
||||
import {
|
||||
ProductEntityType,
|
||||
type ProductMetaInfo,
|
||||
} from '@coze-arch/bot-api/product_api';
|
||||
import { DeveloperApi, PlaygroundApi, ProductApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { SelectSpaceModal } from '../select-space-modal';
|
||||
|
||||
const botDuplicateEvent = createReportEvent({
|
||||
eventName: ReportEventNames.botDuplicate,
|
||||
});
|
||||
|
||||
interface DuplicateBotProps {
|
||||
storeCategory?: ProductMetaInfo['category'];
|
||||
botName?: string;
|
||||
botID?: string;
|
||||
isDisabled?: boolean;
|
||||
btnTxt?: string;
|
||||
pageFrom?: BotPageFromEnum;
|
||||
version?: string;
|
||||
buttonSize?: Size;
|
||||
enableCozeDesign?: boolean;
|
||||
/**
|
||||
* cozeDesign 的情况下才生效
|
||||
*/
|
||||
isBlock?: boolean;
|
||||
eventCallbacks?: Partial<{
|
||||
clickButton: () => void;
|
||||
duplicateFinished: ({ newBotId }: { newBotId: string }) => void;
|
||||
}>;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line -- Needs to be refactored
|
||||
export const DuplicateBot: FC<DuplicateBotProps> = ({
|
||||
storeCategory,
|
||||
botName,
|
||||
botID,
|
||||
isDisabled,
|
||||
btnTxt,
|
||||
pageFrom,
|
||||
version,
|
||||
buttonSize,
|
||||
enableCozeDesign,
|
||||
isBlock,
|
||||
eventCallbacks,
|
||||
}) => {
|
||||
const {
|
||||
space: { hide_operation, id: spaceID },
|
||||
getPersonalSpaceID,
|
||||
} = useSpaceStore();
|
||||
const { spaces: list = [] } = useSpaceList();
|
||||
|
||||
const { pageFromFromStore } = usePageRuntimeStore(
|
||||
useShallow(state => ({
|
||||
pageFromFromStore: state.pageFrom,
|
||||
})),
|
||||
);
|
||||
const { botIdFromStore, botNameFromStore } = useBotInfoStore(
|
||||
useShallow(state => ({
|
||||
botIdFromStore: state.botId,
|
||||
botNameFromStore: state.name,
|
||||
})),
|
||||
);
|
||||
const [showSpaceModal, setShowSpaceModal] = useState(false);
|
||||
|
||||
const { runAsync: copyAndOpenBot } = useRequest(
|
||||
// eslint-disable-next-line complexity
|
||||
async (targetSpaceId?: string, name?: string): Promise<string> => {
|
||||
botDuplicateEvent.start();
|
||||
|
||||
let resp: {
|
||||
code?: string | number;
|
||||
msg?: string;
|
||||
data?: { bot_id?: string };
|
||||
};
|
||||
if (
|
||||
(pageFrom === BotPageFromEnum.Store ||
|
||||
pageFrom === BotPageFromEnum.Template) &&
|
||||
botID &&
|
||||
version &&
|
||||
targetSpaceId
|
||||
) {
|
||||
if (pageFrom === BotPageFromEnum.Template) {
|
||||
const {
|
||||
code,
|
||||
message,
|
||||
data: { new_entity_id: newBotId } = {},
|
||||
} = await ProductApi.PublicDuplicateProduct({
|
||||
product_id: botID,
|
||||
entity_type: ProductEntityType.BotTemplate,
|
||||
space_id: targetSpaceId,
|
||||
name: name ?? '',
|
||||
});
|
||||
resp = {
|
||||
code,
|
||||
msg: message,
|
||||
data: {
|
||||
bot_id: newBotId,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
resp = await PlaygroundApi.DuplicateBotVersionToSpace({
|
||||
bot_id: botID,
|
||||
version,
|
||||
target_space_id: targetSpaceId,
|
||||
name: name ?? '',
|
||||
});
|
||||
}
|
||||
|
||||
//复制完成,关闭空间弹窗
|
||||
setShowSpaceModal(false);
|
||||
} else if (pageFromFromStore === BotPageFromEnum.Explore) {
|
||||
//explore时可以复制到某个空间下
|
||||
resp = await DeveloperApi.DuplicateBotToSpace({
|
||||
draft_bot_id: botIdFromStore,
|
||||
target_space_id: targetSpaceId || '',
|
||||
name,
|
||||
});
|
||||
|
||||
//复制完成,关闭空间弹窗
|
||||
setShowSpaceModal(false);
|
||||
} else {
|
||||
resp = await SpaceApi.DuplicateDraftBot({
|
||||
bot_id: botIdFromStore,
|
||||
});
|
||||
}
|
||||
|
||||
eventCallbacks?.duplicateFinished?.({
|
||||
newBotId: resp.data?.bot_id ?? '',
|
||||
});
|
||||
|
||||
const botTeaparams = {
|
||||
bot_type:
|
||||
pageFromFromStore === BotPageFromEnum.Explore ||
|
||||
pageFromFromStore === BotPageFromEnum.Store
|
||||
? 'store_bot'
|
||||
: 'team_bot',
|
||||
bot_id: botID ?? botIdFromStore,
|
||||
workspace_type:
|
||||
pageFromFromStore === BotPageFromEnum.Store
|
||||
? 'store_workspace'
|
||||
: getPersonalSpaceID() === targetSpaceId
|
||||
? 'personal_workspace'
|
||||
: 'team_workspace',
|
||||
bot_name: botName ?? botNameFromStore ?? '',
|
||||
};
|
||||
if (resp.code === 0) {
|
||||
sendTeaEvent(EVENT_NAMES.bot_duplicate_result, {
|
||||
...botTeaparams,
|
||||
result: 'success',
|
||||
});
|
||||
} else {
|
||||
sendTeaEvent(EVENT_NAMES.bot_duplicate_result, {
|
||||
...botTeaparams,
|
||||
result: 'failed',
|
||||
error_code: resp.code,
|
||||
error_message: resp.msg,
|
||||
});
|
||||
}
|
||||
|
||||
const respData = resp.data;
|
||||
|
||||
if (!respData) {
|
||||
throw new CustomError(
|
||||
ReportEventNames.botDuplicate,
|
||||
I18n.t('bot_copy_info_error'),
|
||||
);
|
||||
}
|
||||
const { bot_id: botId } = respData;
|
||||
if (!botID && !botIdFromStore) {
|
||||
throw new CustomError(
|
||||
ReportEventNames.botDuplicate,
|
||||
I18n.t('bot_copy_id_error'),
|
||||
);
|
||||
}
|
||||
|
||||
const url = `${location.origin}/space/${
|
||||
targetSpaceId || spaceID
|
||||
}/bot/${botId}?from=copy`;
|
||||
|
||||
return url;
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: () => {
|
||||
botDuplicateEvent.success();
|
||||
},
|
||||
onError: e => {
|
||||
botDuplicateEvent.error({ error: e, reason: e.message });
|
||||
setShowSpaceModal(false);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const beforeCopyClick = () => {
|
||||
eventCallbacks?.clickButton?.();
|
||||
sendTeaEvent(EVENT_NAMES.bot_duplicate_click, {
|
||||
bot_type:
|
||||
pageFromFromStore === BotPageFromEnum.Bot ? 'team_bot' : 'store_bot',
|
||||
});
|
||||
|
||||
if (pageFrom === BotPageFromEnum.Store) {
|
||||
sendTeaEvent(EVENT_NAMES.bot_duplicate_click_front, {
|
||||
bot_type: 'store_bot',
|
||||
bot_id: botID,
|
||||
bot_name: botName,
|
||||
category_id: storeCategory?.id,
|
||||
category_name: storeCategory?.name,
|
||||
source: 'bots_store',
|
||||
from: getParamsFromQuery({ key: 'from' }),
|
||||
});
|
||||
setShowSpaceModal(true);
|
||||
} else if (pageFromFromStore === BotPageFromEnum.Explore) {
|
||||
sendTeaEvent(EVENT_NAMES.bot_duplicate_click_front, {
|
||||
bot_type: 'store_bot',
|
||||
bot_id: botNameFromStore,
|
||||
bot_name: botNameFromStore,
|
||||
source: 'explore_bot_detailpage',
|
||||
from: 'explore_card',
|
||||
});
|
||||
sendTeaEvent(EVENT_NAMES.click_bot_duplicate, {
|
||||
bot_id: botIdFromStore,
|
||||
bot_name: botNameFromStore,
|
||||
from: 'explore_card',
|
||||
source: 'explore_bot_detailpage',
|
||||
});
|
||||
//探索页面来源:team>1时选择copy 空间,否则copy到个人空间
|
||||
if (list.length === 1) {
|
||||
openNewWindow(() => copyAndOpenBot(list?.[0].id));
|
||||
} else {
|
||||
setShowSpaceModal(true);
|
||||
}
|
||||
} else if (pageFrom === BotPageFromEnum.Template) {
|
||||
//探索页面来源:team>1时选择copy 空间,否则copy到个人空间
|
||||
if (list.length === 1) {
|
||||
openNewWindow(() => copyAndOpenBot(list?.[0].id));
|
||||
} else {
|
||||
setShowSpaceModal(true);
|
||||
}
|
||||
} else {
|
||||
sendTeaEvent(EVENT_NAMES.bot_duplicate_click_front, {
|
||||
bot_type: 'team_bot',
|
||||
bot_id: botIdFromStore,
|
||||
bot_name: botNameFromStore,
|
||||
source: 'bots_detailpage',
|
||||
from: 'bots_card',
|
||||
});
|
||||
// bot页面来源:若有操作权限直接copy到当前空间下
|
||||
if (hide_operation) {
|
||||
Toast.warning('Bot in public space cannot duplicate');
|
||||
return;
|
||||
} else {
|
||||
openNewWindow(copyAndOpenBot);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{enableCozeDesign ? (
|
||||
<Button
|
||||
type="primary"
|
||||
theme="solid"
|
||||
size={buttonSize}
|
||||
onClick={beforeCopyClick}
|
||||
disabled={isDisabled}
|
||||
block={isBlock}
|
||||
>
|
||||
{btnTxt || I18n.t('duplicate')}
|
||||
</Button>
|
||||
) : (
|
||||
<UIButton
|
||||
type="primary"
|
||||
theme="solid"
|
||||
size={buttonSize}
|
||||
onClick={beforeCopyClick}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{btnTxt || I18n.t('duplicate')}
|
||||
</UIButton>
|
||||
)}
|
||||
|
||||
{/* 选择空间弹窗 */}
|
||||
<SelectSpaceModal
|
||||
botName={botName ?? botNameFromStore}
|
||||
visible={showSpaceModal}
|
||||
onCancel={() => {
|
||||
setShowSpaceModal(false);
|
||||
}}
|
||||
onConfirm={(id, name) => {
|
||||
sendTeaEvent(EVENT_NAMES.click_create_bot_confirm, {
|
||||
click: 'success',
|
||||
create_type: 'duplicate',
|
||||
from: 'explore_card',
|
||||
source: 'explore_bot_detailpage',
|
||||
});
|
||||
openNewWindow(() => copyAndOpenBot(id, name));
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const isMacOS = /(Macintosh|MacIntel|MacPPC|Mac68K|iPad)/.test(
|
||||
navigator.userAgent,
|
||||
);
|
||||
|
||||
export const SHORTCUTS = {
|
||||
CTRL: isMacOS ? '⌘' : 'Ctrl',
|
||||
SHIFT: isMacOS ? '⇧' : '⇧',
|
||||
ALT: isMacOS ? '⌥' : 'Alt',
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
.item {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
color: #1D1C23;
|
||||
}
|
||||
|
||||
.itemTitle {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.itemContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
right: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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 React, { type ReactNode } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { getIsIPad } from '@coze-arch/bot-utils';
|
||||
import { Divider, Typography, Tag } from '@coze-arch/bot-semi';
|
||||
import { IconCloseNoCycle } from '@coze-arch/bot-icons';
|
||||
|
||||
import { SHORTCUTS } from './constants';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface ShortcutItemProps {
|
||||
title: ReactNode;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
function ShortcutItem({ title, children }: ShortcutItemProps) {
|
||||
return (
|
||||
<div className={s.item}>
|
||||
<div className={s.itemTitle}>{title}</div>
|
||||
<div className={s.itemContent}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DividerWithMargin() {
|
||||
return <Divider style={{ margin: '12px 0' }} />;
|
||||
}
|
||||
|
||||
function ShortcutTag({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<Tag
|
||||
style={{
|
||||
margin: '0 4px',
|
||||
height: 24,
|
||||
padding: '0 8px',
|
||||
fontSize: 14,
|
||||
backgroundColor: '#f0f0f5',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
interface FlowShortcutsHelpProps {
|
||||
closable?: boolean;
|
||||
onClose?: () => void;
|
||||
isAgentFlow?: boolean;
|
||||
}
|
||||
|
||||
const isIPad = getIsIPad();
|
||||
|
||||
function FlowShortcutsHelp(props: FlowShortcutsHelpProps) {
|
||||
const { closable = false, onClose, isAgentFlow = false } = props;
|
||||
return (
|
||||
<>
|
||||
{closable ? (
|
||||
<div className={s.close} onClick={() => onClose?.()}>
|
||||
<IconCloseNoCycle />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Typography.Title heading={5} style={{ marginBottom: 16 }}>
|
||||
{I18n.t('flowcanvas_shortcuts_shortcuts')}
|
||||
</Typography.Title>
|
||||
|
||||
<ShortcutItem title={I18n.t('flowcanvas_shortcuts_move_canvas')}>
|
||||
<ShortcutTag>{I18n.t('flowcanvas_shortcuts_space')}</ShortcutTag>
|
||||
<ShortcutTag>{I18n.t('flowcanvas_shortcuts_drag')}</ShortcutTag>
|
||||
</ShortcutItem>
|
||||
|
||||
<DividerWithMargin />
|
||||
|
||||
<ShortcutItem
|
||||
title={
|
||||
<>
|
||||
{I18n.t('flowcanvas_shortcuts_multiple_select')}/
|
||||
{I18n.t('flowcanvas_shortcuts_multiple_deselect')}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ShortcutTag>
|
||||
{SHORTCUTS.CTRL}/{SHORTCUTS.SHIFT}
|
||||
</ShortcutTag>
|
||||
<ShortcutTag>{I18n.t('flowcanvas_shortcuts_click')}</ShortcutTag>
|
||||
</ShortcutItem>
|
||||
|
||||
<DividerWithMargin />
|
||||
|
||||
<ShortcutItem title={I18n.t('flowcanvas_shortcuts_zoom_in')}>
|
||||
<ShortcutTag>{SHORTCUTS.CTRL}</ShortcutTag>
|
||||
<ShortcutTag>+</ShortcutTag>
|
||||
<span style={{ margin: '0 6px' }}>
|
||||
{I18n.t('flowcanvas_shortcuts_or')}
|
||||
</span>
|
||||
<ShortcutTag>{SHORTCUTS.CTRL}</ShortcutTag>
|
||||
<ShortcutTag>{I18n.t('flowcanvas_shortcuts_scroll')}</ShortcutTag>
|
||||
</ShortcutItem>
|
||||
|
||||
<DividerWithMargin />
|
||||
|
||||
<ShortcutItem title={I18n.t('flowcanvas_shortcuts_zoom_out')}>
|
||||
<ShortcutTag>{SHORTCUTS.CTRL}</ShortcutTag>
|
||||
<ShortcutTag>-</ShortcutTag>
|
||||
<span style={{ margin: '0 6px' }}>
|
||||
{I18n.t('flowcanvas_shortcuts_or')}
|
||||
</span>
|
||||
<ShortcutTag>{SHORTCUTS.CTRL}</ShortcutTag>
|
||||
<ShortcutTag>{I18n.t('flowcanvas_shortcuts_scroll')}</ShortcutTag>
|
||||
</ShortcutItem>
|
||||
|
||||
<DividerWithMargin />
|
||||
|
||||
{isIPad || isAgentFlow ? null : (
|
||||
<>
|
||||
<ShortcutItem title={I18n.t('flowcanvas_shortcuts_duplicate')}>
|
||||
<ShortcutTag>{SHORTCUTS.ALT}</ShortcutTag>
|
||||
<ShortcutTag>{I18n.t('flowcanvas_shortcuts_drag')}</ShortcutTag>
|
||||
</ShortcutItem>
|
||||
<DividerWithMargin />
|
||||
</>
|
||||
)}
|
||||
|
||||
<ShortcutItem title={I18n.t('flowcanvas_shortcuts_copy')}>
|
||||
<ShortcutTag>{SHORTCUTS.CTRL}</ShortcutTag>
|
||||
<ShortcutTag>C</ShortcutTag>
|
||||
</ShortcutItem>
|
||||
|
||||
<DividerWithMargin />
|
||||
|
||||
<ShortcutItem title={I18n.t('flowcanvas_shortcuts_paste')}>
|
||||
<ShortcutTag>{SHORTCUTS.CTRL}</ShortcutTag>
|
||||
<ShortcutTag>V</ShortcutTag>
|
||||
</ShortcutItem>
|
||||
|
||||
<DividerWithMargin />
|
||||
|
||||
<UndoRedoShortcuts />
|
||||
|
||||
<ShortcutItem title={I18n.t('flowcanvas_shortcuts_delete')}>
|
||||
<ShortcutTag>{I18n.t('flowcanvas_shortcuts_backspace')}</ShortcutTag>
|
||||
</ShortcutItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function UndoRedoShortcuts() {
|
||||
return (
|
||||
<>
|
||||
<ShortcutItem title={I18n.t('workflow_detail_undo_tooltip')}>
|
||||
<ShortcutTag>{SHORTCUTS.CTRL}</ShortcutTag>
|
||||
<ShortcutTag>Z</ShortcutTag>
|
||||
</ShortcutItem>
|
||||
|
||||
<DividerWithMargin />
|
||||
|
||||
<ShortcutItem title={I18n.t('workflow_detail_redo_tooltip')}>
|
||||
<ShortcutTag>{SHORTCUTS.CTRL}</ShortcutTag>
|
||||
<ShortcutTag>{SHORTCUTS.SHIFT}</ShortcutTag>
|
||||
<ShortcutTag>Z</ShortcutTag>
|
||||
</ShortcutItem>
|
||||
|
||||
<DividerWithMargin />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export { FlowShortcutsHelp };
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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个gif,20个静态图,根据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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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 { useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozImage, IconCozUpload } from '@coze-arch/coze-design/icons';
|
||||
import { Button, Image, Popover, Toast, Upload } from '@coze-arch/coze-design';
|
||||
import { FileBizType } from '@coze-arch/bot-api/developer_api';
|
||||
import { customUploadRequest } from '@coze-common/biz-components/picture-upload';
|
||||
|
||||
import { type ImageItem, ImageList } from '../image-list';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface ImagePickerProps {
|
||||
setImage: (image: ImageItem) => void;
|
||||
imageList: ImageItem[];
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export default function ImagePicker(props: ImagePickerProps) {
|
||||
const { setImage, imageList, url } = props;
|
||||
const [uploadBtnLoading, setUploadBtnLoading] = useState(false);
|
||||
return (
|
||||
<div className={s['image-ctn']}>
|
||||
<Popover
|
||||
position="right"
|
||||
trigger="hover"
|
||||
keepDOM
|
||||
content={
|
||||
<div className={s['upload-panel']}>
|
||||
<ImageList
|
||||
data={imageList || []}
|
||||
className={s['image-list']}
|
||||
imageItemClassName={s['image-item']}
|
||||
showDeleteIcon={false}
|
||||
showSelectedIcon={false}
|
||||
onClick={({ item }) => {
|
||||
setImage(item);
|
||||
}}
|
||||
/>
|
||||
<Upload
|
||||
action=""
|
||||
limit={1}
|
||||
customRequest={options => {
|
||||
customUploadRequest({
|
||||
...options,
|
||||
fileBizType: FileBizType.BIZ_BOT_ICON,
|
||||
onSuccess(data) {
|
||||
setImage({
|
||||
img_info: {
|
||||
tar_uri: data?.upload_uri || '',
|
||||
tar_url: data?.upload_url || '',
|
||||
},
|
||||
});
|
||||
},
|
||||
beforeUploadCustom() {
|
||||
setUploadBtnLoading(true);
|
||||
},
|
||||
afterUploadCustom() {
|
||||
setUploadBtnLoading(false);
|
||||
},
|
||||
});
|
||||
}}
|
||||
fileList={[]}
|
||||
accept=".jpeg,.jpg,.png"
|
||||
showReplace={false}
|
||||
showUploadList={false}
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
maxSize={2 * 1024}
|
||||
onSizeError={() => {
|
||||
Toast.error({
|
||||
// starling 切换
|
||||
content: withSlardarIdButton(
|
||||
I18n.t(
|
||||
'dataset_upload_image_warning',
|
||||
{},
|
||||
'Please upload an image less than 2MB',
|
||||
),
|
||||
),
|
||||
showClose: false,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
color="primary"
|
||||
size="small"
|
||||
loading={uploadBtnLoading}
|
||||
icon={<IconCozUpload />}
|
||||
>
|
||||
{I18n.t('creat_popup_profilepicture_upload')}
|
||||
</Button>
|
||||
</Upload>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{url ? (
|
||||
<div>
|
||||
<Image src={url} className={s['show-image']} preview={false} />
|
||||
</div>
|
||||
) : (
|
||||
<div className={classNames(s['empty-image'])}>
|
||||
<IconCozImage />
|
||||
</div>
|
||||
)}
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
.ctn {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.image-ctn {
|
||||
line-height: 0;
|
||||
|
||||
.show-image img,
|
||||
.empty-image {
|
||||
width: 108px;
|
||||
height: 108px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.empty-image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
font-size: 20px;
|
||||
color: var(--coz-fg-secondary, #06070980);
|
||||
|
||||
background-color: var(--coz-mg-primary, #0607090A);
|
||||
}
|
||||
}
|
||||
|
||||
.text-ctn {
|
||||
position: relative;
|
||||
flex: 1 0 0;
|
||||
height: 108px;
|
||||
margin-left: 16px;
|
||||
|
||||
:global {
|
||||
.semi-input-textarea-wrapper {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
|
||||
.semi-input-textarea {
|
||||
height: 108px;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.semi-input-textarea::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.generate-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-panel {
|
||||
padding: 16px;
|
||||
|
||||
background: var(--coz-bg-max, #FFF);
|
||||
border: 0.5px solid var(--coz-stroke-primary, rgba(6, 7, 9, 10%));
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 25%);
|
||||
|
||||
.image-list {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.image-item {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-button-content-right {
|
||||
margin-left: 4px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
153
frontend/packages/studio/components/src/generate-gif/index.tsx
Normal file
153
frontend/packages/studio/components/src/generate-gif/index.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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 { useParams } from 'react-router-dom';
|
||||
import { type CSSProperties, useMemo } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { avatarBackgroundWebSocket } from '@coze-studio/bot-detail-store';
|
||||
import { logger } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
||||
import { PicType } from '@coze-arch/bot-api/playground_api';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
import webSocketManager from '@coze-common/websocket-manager-adapter';
|
||||
import { TextArea } from '@coze-arch/coze-design';
|
||||
|
||||
import { type ImageItem } from '../image-list';
|
||||
import { GenerateButton } from '../generate-button';
|
||||
import ImagePicker from './image-picker';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface GenerateGifProps {
|
||||
scene: 'background' | 'avatar';
|
||||
image: ImageItem; // 默认图片
|
||||
text: string; // 默认文本
|
||||
loading: boolean; // 生成时,socket全局监听服务端响应,因此loading需要受控
|
||||
imageList?: ImageItem[]; // 图片候选列表(只包含静态图)
|
||||
generatingTaskId: string; // 生成中的任务id
|
||||
exceedMaxImageCount?: boolean;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
setImage: (image: ImageItem) => void;
|
||||
setText: (text: string) => void;
|
||||
setLoading: (loading: boolean) => void;
|
||||
setGeneratingTaskId: (id: string) => void;
|
||||
}
|
||||
|
||||
export const GenerateGif: React.FC<GenerateGifProps> = ({
|
||||
image = {
|
||||
id: '',
|
||||
img_info: {
|
||||
tar_uri: '',
|
||||
tar_url: '',
|
||||
},
|
||||
},
|
||||
text = '',
|
||||
loading,
|
||||
imageList = [],
|
||||
exceedMaxImageCount = false,
|
||||
className,
|
||||
generatingTaskId,
|
||||
style,
|
||||
setLoading,
|
||||
setImage,
|
||||
setText,
|
||||
setGeneratingTaskId,
|
||||
scene,
|
||||
}) => {
|
||||
const { bot_id = '0' } = useParams<DynamicParams>();
|
||||
|
||||
const { tar_url: url, tar_uri: uri } = image.img_info ?? {};
|
||||
const hasEmptyValue = !image?.img_info?.tar_url || !text?.trim();
|
||||
const filterImageList = useMemo(
|
||||
() => imageList.filter(item => item?.img_info?.tar_url !== url),
|
||||
[imageList, url],
|
||||
);
|
||||
let tooltipText = '';
|
||||
if (exceedMaxImageCount) {
|
||||
tooltipText = I18n.t('profilepicture_popup_toast_picturemax');
|
||||
}
|
||||
return (
|
||||
<div className={classNames(s.ctn, className)} style={style}>
|
||||
<ImagePicker setImage={setImage} url={url} imageList={filterImageList} />
|
||||
<div className={s['text-ctn']}>
|
||||
<TextArea
|
||||
rows={5}
|
||||
value={text}
|
||||
maxLength={400}
|
||||
className={s['text-area']}
|
||||
placeholder={I18n.t('profilepicture_popup_generategif_default')}
|
||||
onChange={value => {
|
||||
setText(value);
|
||||
}}
|
||||
/>
|
||||
<GenerateButton
|
||||
scene="gif"
|
||||
className={s['generate-btn']}
|
||||
disabled={exceedMaxImageCount || hasEmptyValue}
|
||||
tooltipText={tooltipText}
|
||||
loading={loading}
|
||||
onClick={async () => {
|
||||
setLoading?.(true);
|
||||
try {
|
||||
avatarBackgroundWebSocket.createConnection();
|
||||
// 只负责发送请求,响应在WebSocket中接收
|
||||
const { data } = await PlaygroundApi.GeneratePic({
|
||||
gen_prompt: {
|
||||
ori_prompt: text.trim(),
|
||||
},
|
||||
image_uri: uri,
|
||||
image_url: url,
|
||||
pic_type:
|
||||
scene === 'avatar' ? PicType.IconGif : PicType.BackgroundGif,
|
||||
bot_id,
|
||||
device_id: webSocketManager.deviceId,
|
||||
});
|
||||
if (data?.task_id) {
|
||||
setGeneratingTaskId?.(data.task_id);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error({
|
||||
eventName: 'fail_to_generate_gif',
|
||||
error: error as Error,
|
||||
});
|
||||
setLoading?.(false);
|
||||
setGeneratingTaskId?.('');
|
||||
}
|
||||
}}
|
||||
onCancel={async () => {
|
||||
if (generatingTaskId) {
|
||||
try {
|
||||
const { code } = await PlaygroundApi.CancelGenerateGif({
|
||||
task_id: generatingTaskId,
|
||||
});
|
||||
if (code === 0) {
|
||||
setLoading?.(false);
|
||||
}
|
||||
} catch (error) {
|
||||
const e =
|
||||
error instanceof Error ? error : new Error(error as string);
|
||||
logger.error({ error: e });
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
.segment-tab {
|
||||
:global {
|
||||
.semi-radio-content {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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 { useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozArrowLeft } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
Collapsible,
|
||||
IconButton,
|
||||
SegmentTab,
|
||||
Space,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { type TabItem } from './type';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface GenerateImageTabProps {
|
||||
// tab列表
|
||||
tabs: TabItem[];
|
||||
// 是否可折叠
|
||||
enableCollapsible?: boolean;
|
||||
// 当前激活的tab
|
||||
activeKey?: string;
|
||||
// 当前激活的tab变化回调
|
||||
onTabChange?: (tabKey: string) => void;
|
||||
// 是否展示wait文案
|
||||
showWaitTip?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export enum GenerateType {
|
||||
Static = 'static',
|
||||
Gif = 'gif',
|
||||
}
|
||||
|
||||
export const GenerateImageTab: React.FC<GenerateImageTabProps> = ({
|
||||
tabs = [],
|
||||
enableCollapsible = false,
|
||||
activeKey,
|
||||
onTabChange,
|
||||
showWaitTip = true,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const [isOpen, setOpen] = useState(true);
|
||||
const toggle = () => {
|
||||
setOpen(!isOpen);
|
||||
};
|
||||
|
||||
// tabPane 不卸载
|
||||
const component = (
|
||||
<div>
|
||||
{tabs.map(item => (
|
||||
<div
|
||||
key={item.value}
|
||||
className={classNames({
|
||||
hidden: activeKey !== item.value,
|
||||
'border-0 border-t border-solid mt-2 coz-stroke-primary': isOpen,
|
||||
})}
|
||||
>
|
||||
{item.component}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'border border-solid coz-stroke-plus coz-bg-max rounded-md w-full coz-fg-plus mt-3 pt-2 pb-4 px-4',
|
||||
{
|
||||
'coz-bg-primary pointer-events-none': disabled,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div className=" flex items-center gap-2 justify-between ">
|
||||
<Space>
|
||||
{enableCollapsible ? (
|
||||
<IconButton
|
||||
className="!bg-transparent hover:!coz-mg-primary-hovered"
|
||||
icon={
|
||||
<IconCozArrowLeft
|
||||
className={classNames(
|
||||
isOpen ? 'rotate-90' : '-rotate-90',
|
||||
'coz-fg-secondary',
|
||||
)}
|
||||
onClick={toggle}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<SegmentTab
|
||||
className={s['segment-tab']}
|
||||
onChange={e => {
|
||||
onTabChange?.(e.target.value);
|
||||
}}
|
||||
options={tabs.map(item => ({
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
}))}
|
||||
defaultValue={activeKey ?? tabs[0]?.value}
|
||||
/>
|
||||
</Space>
|
||||
|
||||
{showWaitTip ? (
|
||||
<div className="coz-fg-dim text-xs flex-1 truncate">
|
||||
{I18n.t('profilepicture_popup_async')}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{enableCollapsible ? (
|
||||
// keepDOM 异常失效使用collapseHeight 不销毁dom保留状态
|
||||
<Collapsible isOpen={isOpen} keepDOM collapseHeight={1}>
|
||||
<div> {component} </div>
|
||||
</Collapsible>
|
||||
) : (
|
||||
<div> {component} </div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 ReactElement } from 'react';
|
||||
|
||||
export interface TabItem {
|
||||
label: string;
|
||||
value: string;
|
||||
component: ReactElement;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
.ctn {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 7px;
|
||||
justify-content: center;
|
||||
|
||||
.image-item {
|
||||
cursor: pointer;
|
||||
|
||||
position: relative;
|
||||
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
object-fit: cover;
|
||||
border: 1.5px solid transparent;
|
||||
border-radius: 8px;
|
||||
|
||||
&.selected {
|
||||
border: 1.5px solid var(--coz-stroke-hglt, #4E40E5);
|
||||
}
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
|
||||
display: none;
|
||||
|
||||
font-size: 16px;
|
||||
color: var(--coz-fg-hglt-red, #F54A45);
|
||||
}
|
||||
|
||||
&:hover .delete-icon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.check-icon {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
bottom: 4px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
background-color: var(--coz-mg-hglt-plus, #4E40E5);
|
||||
border-radius: 4px;
|
||||
|
||||
svg {
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
119
frontend/packages/studio/components/src/image-list/index.tsx
Normal file
119
frontend/packages/studio/components/src/image-list/index.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
IconCozCheckMarkFill,
|
||||
IconCozMinusCircleFillPalette,
|
||||
} from '@coze-arch/coze-design/icons';
|
||||
import { type PicTask } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export type ImageItem = PicTask;
|
||||
|
||||
export interface ImageListProps {
|
||||
selectedKey?: string; // 选中的key
|
||||
data: ImageItem[]; // 列表数据
|
||||
className?: string;
|
||||
imageItemClassName?: string;
|
||||
showDeleteIcon?: boolean;
|
||||
showSelectedIcon?: boolean;
|
||||
style?: CSSProperties;
|
||||
onRemove?: (params: {
|
||||
index?: number;
|
||||
item?: ImageItem;
|
||||
data: ImageItem[];
|
||||
}) => void; // 删除图片,data是此次删除之后的数据
|
||||
onSelect?: (params: {
|
||||
index?: number;
|
||||
item: ImageItem;
|
||||
data: ImageItem[];
|
||||
selected: boolean;
|
||||
}) => void; // 选中图片,其中item和data都是此次选中之前的数据,selected表示在本次选中之前此图片是否已是选中状态
|
||||
onClick?: (params: {
|
||||
index: number;
|
||||
item: ImageItem;
|
||||
data: ImageItem[];
|
||||
}) => void; // 点击图片
|
||||
}
|
||||
|
||||
export const ImageList: React.FC<ImageListProps> = ({
|
||||
data,
|
||||
showDeleteIcon = true,
|
||||
showSelectedIcon = true,
|
||||
className,
|
||||
imageItemClassName,
|
||||
style,
|
||||
onSelect,
|
||||
onRemove,
|
||||
onClick,
|
||||
selectedKey,
|
||||
}) => {
|
||||
if (!data || data.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={classNames(className, s.ctn)} style={style}>
|
||||
{data.map((item, index) => {
|
||||
const { img_info } = item;
|
||||
const { tar_uri, tar_url } = img_info ?? {};
|
||||
return (
|
||||
<div
|
||||
key={tar_uri}
|
||||
className={classNames(s['image-item'], imageItemClassName)}
|
||||
>
|
||||
<img
|
||||
src={tar_url}
|
||||
alt="图片"
|
||||
className={classNames({
|
||||
[s.selected]: showSelectedIcon && selectedKey === tar_uri,
|
||||
})}
|
||||
onClick={() => {
|
||||
onClick?.({ index, item, data });
|
||||
onSelect?.({
|
||||
index,
|
||||
item,
|
||||
data,
|
||||
selected: selectedKey === tar_uri,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{showDeleteIcon ? (
|
||||
<IconCozMinusCircleFillPalette
|
||||
className={s['delete-icon']}
|
||||
onClick={() => {
|
||||
onRemove?.({
|
||||
index,
|
||||
item,
|
||||
data: data.filter(i => i !== item),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{showSelectedIcon && selectedKey === tar_uri ? (
|
||||
<div className={s['check-icon']}>
|
||||
<IconCozCheckMarkFill />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
72
frontend/packages/studio/components/src/index.ts
Normal file
72
frontend/packages/studio/components/src/index.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { AvatarBackgroundNoticeDot } from './avatar-background-notice-dot';
|
||||
|
||||
export { ImageList, type ImageItem, type ImageListProps } from './image-list';
|
||||
export { GenerateButton } from './generate-button';
|
||||
|
||||
export {
|
||||
InputWithCountField,
|
||||
InputWithCount,
|
||||
type InputWithCountProps,
|
||||
} from './input-with-count';
|
||||
export { UIBreadcrumb, type BreadCrumbProps } from './ui-breadcrumb';
|
||||
export { type UISearchProps, UISearch } from './ui-search';
|
||||
export { PopoverContent } from './popover-content';
|
||||
|
||||
export { SelectSpaceModal } from './select-space-modal';
|
||||
export { DuplicateBot } from './duplicate-bot';
|
||||
export { CozeBrand, type CozeBrandProps } from './coze-brand';
|
||||
|
||||
export { CardThumbnailPopover } from './card-thumbnail-popover';
|
||||
|
||||
export { LinkList, type LinkListItem } from './link-list';
|
||||
export { AvatarName } from './avatar-name';
|
||||
export { TopBar as PersonalHeader } from './personal-header';
|
||||
|
||||
export { Carousel } from './carousel';
|
||||
export {
|
||||
GenerateImageTab,
|
||||
GenerateType,
|
||||
type GenerateImageTabProps,
|
||||
} from './generate-img-tab';
|
||||
export { FlowShortcutsHelp } from './flow-shortcuts-help';
|
||||
export { LoadingButton } from './loading-button';
|
||||
export { Search, SearchProps } from './search';
|
||||
|
||||
export { ResizableLayout } from './resizable-layout';
|
||||
|
||||
export { ModelOptionItem } from './model-option/option-item';
|
||||
export { InputSlider, InputSliderProps } from './input-controls/input-slider';
|
||||
export { UploadGenerateButton } from './upload-generate-button';
|
||||
|
||||
export { usePluginLimitModal, transPricingRules } from './plugin-limit-info';
|
||||
|
||||
// 曝光埋点上报组件,进入视图上报
|
||||
export { TeaExposure } from './tea-exposure';
|
||||
export { Sticky } from './sticky';
|
||||
|
||||
export {
|
||||
ProjectTemplateCopyModal,
|
||||
type ProjectTemplateCopyValue,
|
||||
useProjectTemplateCopyModal,
|
||||
appendCopySuffix,
|
||||
} from './project-duplicate-modal';
|
||||
export { SpaceFormSelect } from './space-form-select';
|
||||
// !Notice 以下模块只允许导出类型,避免首屏加载 react-dnd,@blueprintjs/core 等相关代码
|
||||
export { type TItemRender, type ITemRenderProps } from './sortable-list';
|
||||
export { type ConnectDnd, type OnMove } from './sortable-list/hooks';
|
||||
@@ -0,0 +1,21 @@
|
||||
.input-slider {
|
||||
display: flex;
|
||||
column-gap: 8px;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
|
||||
:global {
|
||||
.semi-slider {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.slider {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-number {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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 { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import { isInteger, isUndefined } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { useHover } from 'ahooks';
|
||||
import { InputNumber } from '@coze-arch/coze-design';
|
||||
import { type SliderProps } from '@coze-arch/bot-semi/Slider';
|
||||
import { Slider } from '@coze-arch/bot-semi';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface InputSliderProps {
|
||||
value?: number;
|
||||
onChange?: (v: number) => void;
|
||||
max?: number;
|
||||
min?: number;
|
||||
step?: number;
|
||||
disabled?: boolean;
|
||||
decimalPlaces?: number;
|
||||
marks?: SliderProps['marks'];
|
||||
className?: string;
|
||||
}
|
||||
const POWVAL = 10;
|
||||
const formateDecimalPlacesString = (
|
||||
value: string | number,
|
||||
prevValue?: number,
|
||||
decimalPlaces?: number,
|
||||
) => {
|
||||
if (isUndefined(decimalPlaces)) {
|
||||
return value.toString();
|
||||
}
|
||||
const numberValue = Number(value);
|
||||
const stringValue = value.toString();
|
||||
if (Number.isNaN(numberValue)) {
|
||||
return `${value}`;
|
||||
}
|
||||
if (decimalPlaces === 0 && !isInteger(Number(value)) && prevValue) {
|
||||
return `${prevValue}`;
|
||||
}
|
||||
const decimalPointIndex = stringValue.indexOf('.');
|
||||
|
||||
if (decimalPointIndex < 0) {
|
||||
return stringValue;
|
||||
}
|
||||
const formattedValue = stringValue.substring(
|
||||
0,
|
||||
decimalPointIndex + 1 + decimalPlaces,
|
||||
);
|
||||
|
||||
if (formattedValue.endsWith('.') && decimalPlaces === 0) {
|
||||
return formattedValue.substring(0, formattedValue.length - 1);
|
||||
}
|
||||
return formattedValue;
|
||||
};
|
||||
|
||||
const formateDecimalPlacesNumber = (
|
||||
value: number,
|
||||
prevValue?: number,
|
||||
decimalPlaces?: number,
|
||||
) => {
|
||||
if (isUndefined(decimalPlaces)) {
|
||||
return value;
|
||||
}
|
||||
if (decimalPlaces === 0 && !isInteger(value) && prevValue) {
|
||||
return prevValue;
|
||||
}
|
||||
const pow = Math.pow(POWVAL, decimalPlaces);
|
||||
return Math.round(value * pow) / pow;
|
||||
};
|
||||
|
||||
export const InputSlider: React.FC<InputSliderProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
max = 1,
|
||||
min = 0,
|
||||
step = 1,
|
||||
disabled,
|
||||
decimalPlaces = 0,
|
||||
className,
|
||||
}) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const hover = useHover(ref);
|
||||
const sliderRenderId = useMemo(() => nanoid(), [max, min, hover]);
|
||||
const [isFocus, setFocus] = useState(false);
|
||||
const [inputRenderId, setInputRenderId] = useState(nanoid());
|
||||
const updateInputNumber = () => {
|
||||
if (isFocus) {
|
||||
return;
|
||||
}
|
||||
setInputRenderId(nanoid());
|
||||
};
|
||||
const onNumberChange = (numberValue: number) => {
|
||||
updateInputNumber();
|
||||
|
||||
// 防止 -0
|
||||
if (numberValue === 0) {
|
||||
onChange?.(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const expectedFormattedValue = formateDecimalPlacesNumber(
|
||||
numberValue,
|
||||
value,
|
||||
decimalPlaces,
|
||||
);
|
||||
|
||||
onChange?.(expectedFormattedValue);
|
||||
};
|
||||
|
||||
// 防止 -0 导致 InputNumber 无限循环更新
|
||||
const fixedValue = Object.is(value, -0) ? 0 : value;
|
||||
|
||||
useEffect(() => {
|
||||
updateInputNumber();
|
||||
}, [isFocus]);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={classNames(styles['input-slider'], className)}>
|
||||
<Slider
|
||||
key={sliderRenderId}
|
||||
className={styles.slider}
|
||||
disabled={disabled}
|
||||
value={fixedValue}
|
||||
max={max}
|
||||
min={min}
|
||||
step={step}
|
||||
showBoundary
|
||||
onChange={v => {
|
||||
if (typeof v === 'number') {
|
||||
onChange?.(v);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<InputNumber
|
||||
onFocus={() => setFocus(true)}
|
||||
onBlur={() => setFocus(false)}
|
||||
key={inputRenderId}
|
||||
className={styles['input-number']}
|
||||
value={fixedValue}
|
||||
disabled={disabled}
|
||||
formatter={inputValue => formateDecimalPlacesString(inputValue, value)}
|
||||
onNumberChange={onNumberChange}
|
||||
max={max}
|
||||
min={min}
|
||||
step={step}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
.limit-count {
|
||||
overflow: hidden;
|
||||
|
||||
padding-right: 12px;
|
||||
padding-left: 8px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
color: rgb(29 28 35 / 60%);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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 { useMemo } from 'react';
|
||||
|
||||
import { type InputProps } from '@coze-arch/bot-semi/Input';
|
||||
import { UIInput, withField } from '@coze-arch/bot-semi';
|
||||
import 'utility-types';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface LimitCountProps {
|
||||
maxLen: number;
|
||||
len: number;
|
||||
}
|
||||
|
||||
const LimitCount: React.FC<LimitCountProps> = ({ maxLen, len }) => (
|
||||
<span className={s['limit-count']}>
|
||||
<span>{len}</span>
|
||||
<span>/</span>
|
||||
<span>{maxLen}</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
export interface InputWithCountProps extends InputProps {
|
||||
// 设置字数限制并显示字数统计
|
||||
getValueLength?: (value?: InputProps['value'] | string) => number;
|
||||
}
|
||||
|
||||
export const InputWithCount: React.FC<InputWithCountProps> = props => {
|
||||
const { value, maxLength, getValueLength } = props;
|
||||
|
||||
const len = useMemo(() => {
|
||||
if (getValueLength) {
|
||||
return getValueLength(value);
|
||||
} else if (value) {
|
||||
return value.toString().length;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}, [value, getValueLength]);
|
||||
|
||||
return (
|
||||
<UIInput
|
||||
{...props}
|
||||
suffix={
|
||||
Boolean(maxLength) && <LimitCount maxLen={maxLength ?? 0} len={len} />
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const InputWithCountField = withField(InputWithCount);
|
||||
@@ -0,0 +1,29 @@
|
||||
.link-list {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.link-list-item {
|
||||
padding: 0 18px;
|
||||
color: rgb(255 255 255 / 60%);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.click-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.pointer {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
frontend/packages/studio/components/src/link-list/index.tsx
Normal file
74
frontend/packages/studio/components/src/link-list/index.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 React, { type ReactNode, type CSSProperties } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Space } from '@coze-arch/bot-semi';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface LinkListItem {
|
||||
extra?: string;
|
||||
icon?: ReactNode;
|
||||
label: string;
|
||||
link?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const LinkList = ({
|
||||
className,
|
||||
style,
|
||||
data,
|
||||
pointerClassName,
|
||||
itemClassName,
|
||||
}: {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
data: LinkListItem[];
|
||||
pointerClassName?: string;
|
||||
itemClassName?: string;
|
||||
}) => (
|
||||
<div className={classNames(s['link-list'], className)} style={style}>
|
||||
{data?.map(item => (
|
||||
<div
|
||||
className={classNames(s['link-list-item'], itemClassName)}
|
||||
key={`link-list-${item.label}`}
|
||||
>
|
||||
{!!item.extra && <span style={{ marginRight: 4 }}>{item.extra}</span>}
|
||||
<div
|
||||
className={classNames(
|
||||
s['click-area'],
|
||||
(item.link || item.onClick) && s.pointer,
|
||||
(item.link || item.onClick) && pointerClassName,
|
||||
)}
|
||||
onClick={() => {
|
||||
if (item.link) {
|
||||
window.open(item.link);
|
||||
} else {
|
||||
item.onClick?.();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Space spacing={4}>
|
||||
{item.icon}
|
||||
{item.label}
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,12 @@
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
background-color: #f7f7fa;
|
||||
padding: 6px 36px;
|
||||
}
|
||||
|
||||
.tool-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
}
|
||||
17
frontend/packages/studio/components/src/list-tab/index.ts
Normal file
17
frontend/packages/studio/components/src/list-tab/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { ListTab, type BotListHeaderProps } from './list-tab';
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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 PropsWithChildren, type ReactNode } from 'react';
|
||||
|
||||
import cs from 'classnames';
|
||||
import { type TabsProps } from '@coze-arch/bot-semi/Tabs';
|
||||
import { Tabs } from '@coze-arch/bot-semi';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface BotListHeaderProps extends TabsProps {
|
||||
toolbar?: ReactNode;
|
||||
containerClass?: string;
|
||||
}
|
||||
|
||||
export const ListTab: React.FC<PropsWithChildren<BotListHeaderProps>> = ({
|
||||
children,
|
||||
toolbar,
|
||||
containerClass,
|
||||
...props
|
||||
}) => (
|
||||
<Tabs
|
||||
{...props}
|
||||
tabPaneMotion={false}
|
||||
type="button"
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention -- react 组件
|
||||
renderTabBar={(innerProps, Node) => (
|
||||
<div className={cs(s.header, containerClass)}>
|
||||
<Node {...innerProps} />
|
||||
<div className={s['tool-bar']}>{toolbar}</div>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Tabs>
|
||||
);
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 React, { forwardRef, useState } from 'react';
|
||||
|
||||
import { isString } from 'lodash-es';
|
||||
import { type ToastReactProps } from '@coze-arch/bot-semi/Toast';
|
||||
import { type ButtonProps } from '@coze-arch/bot-semi/Button';
|
||||
import { UIButton, Toast, Spin } from '@coze-arch/bot-semi';
|
||||
|
||||
export type LoadingButtonProps = ButtonProps & {
|
||||
/** 加载中的 toast 文案 */
|
||||
loadingToast?: string | Omit<ToastReactProps, 'type'>;
|
||||
};
|
||||
|
||||
export const LoadingButton: React.ForwardRefExoticComponent<LoadingButtonProps> =
|
||||
forwardRef<UIButton, LoadingButtonProps>(
|
||||
({ loadingToast, ...buttonProps }, ref) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const onClick: React.MouseEventHandler<
|
||||
HTMLButtonElement
|
||||
> = async event => {
|
||||
let toastId = '';
|
||||
try {
|
||||
if (loadingToast) {
|
||||
toastId = Toast.info({
|
||||
icon: <Spin />,
|
||||
showClose: false,
|
||||
duration: 0,
|
||||
...(isString(loadingToast)
|
||||
? { content: loadingToast }
|
||||
: loadingToast),
|
||||
});
|
||||
}
|
||||
setLoading(true);
|
||||
if (buttonProps.onClick) {
|
||||
await buttonProps.onClick(event);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
if (toastId) {
|
||||
Toast.close(toastId);
|
||||
}
|
||||
}
|
||||
};
|
||||
return (
|
||||
<UIButton
|
||||
ref={ref}
|
||||
loading={loading}
|
||||
{...buttonProps}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,18 @@
|
||||
.action-bar {
|
||||
display: flex;
|
||||
column-gap: 8px;
|
||||
align-items: center;
|
||||
|
||||
.icon-button {
|
||||
font-size: 16px;
|
||||
// rgb mark
|
||||
color: #060709;
|
||||
|
||||
&.icon-button-active {
|
||||
// rgb mark
|
||||
background-color: var(--semi-color-fill-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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 @coze-arch/no-deep-relative-import -- svg */
|
||||
import { useState, type ComponentProps, type CSSProperties } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Toast, Tooltip, UIButton, Upload } from '@coze-arch/bot-semi';
|
||||
import { IconLinkStroked } from '@coze-arch/bot-icons';
|
||||
|
||||
import {
|
||||
InsertLinkPopover,
|
||||
type InsertLinkPopoverProps,
|
||||
} from '../insert-link-popover';
|
||||
import { getFixedVariableTemplate } from '../../utils/onboarding-variable';
|
||||
import { type TriggerAction } from '../../type';
|
||||
import { getIsFileFormatValid } from '../../helpers/get-is-file-format-valid';
|
||||
import { OnboardingVariable } from '../../constant/onboarding-variable';
|
||||
import { getFileSizeReachLimitI18n, MAX_FILE_SIZE } from '../../constant/file';
|
||||
import { ReactComponent as IconMemberOutlined } from '../../../../assets/icon_member_outlined.svg';
|
||||
import { ReactComponent as IconImageOutlined } from '../../../../assets/icon_image_outlined.svg';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
const SHOW_ADD_NAME_BTN = false;
|
||||
|
||||
export interface ActionBarProps {
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
onTriggerAction?: (action: TriggerAction) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const iconButtonProps: ComponentProps<typeof UIButton> = {
|
||||
size: 'small',
|
||||
type: 'tertiary',
|
||||
theme: 'borderless',
|
||||
className: styles['icon-button'],
|
||||
};
|
||||
export const ActionBar: React.FC<ActionBarProps> = ({
|
||||
className,
|
||||
style,
|
||||
onTriggerAction,
|
||||
disabled,
|
||||
}) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const togglePopoverVisible = () => {
|
||||
setVisible(e => !e);
|
||||
};
|
||||
|
||||
const closePopover = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const onConfirmInsertLink: InsertLinkPopoverProps['onConfirm'] = param => {
|
||||
closePopover();
|
||||
onTriggerAction?.({ type: 'link', sync: true, payload: param });
|
||||
};
|
||||
|
||||
const onInsertImage = (file: File) => {
|
||||
onTriggerAction?.({ type: 'image', payload: { file }, sync: false });
|
||||
};
|
||||
|
||||
const onInsertVariable = () => {
|
||||
onTriggerAction?.({
|
||||
type: 'variable',
|
||||
payload: {
|
||||
variableTemplate: getFixedVariableTemplate(
|
||||
OnboardingVariable.USER_NAME,
|
||||
),
|
||||
},
|
||||
sync: true,
|
||||
});
|
||||
};
|
||||
|
||||
const showFileTypeInvalidToast = () =>
|
||||
Toast.warning({
|
||||
showClose: false,
|
||||
content: I18n.t('file_format_not_supported'),
|
||||
});
|
||||
|
||||
const showFileSizeInvalidToast = () =>
|
||||
Toast.warning({
|
||||
showClose: false,
|
||||
content: getFileSizeReachLimitI18n(),
|
||||
});
|
||||
const onFileChange = (files: File[]) => {
|
||||
const file = files.at(0);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!getIsFileFormatValid(file)) {
|
||||
showFileTypeInvalidToast();
|
||||
return;
|
||||
}
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
showFileSizeInvalidToast();
|
||||
return;
|
||||
}
|
||||
onInsertImage(file);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames(className, styles['action-bar'])} style={style}>
|
||||
<Upload
|
||||
accept="image/*"
|
||||
limit={1}
|
||||
onFileChange={onFileChange}
|
||||
action="/"
|
||||
fileList={[]}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Tooltip disableFocusListener content={I18n.t('add_image')}>
|
||||
<UIButton
|
||||
{...iconButtonProps}
|
||||
icon={<IconImageOutlined />}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Upload>
|
||||
|
||||
<InsertLinkPopover
|
||||
visible={visible}
|
||||
onClickOutSide={closePopover}
|
||||
onConfirm={onConfirmInsertLink}
|
||||
>
|
||||
<span>
|
||||
<Tooltip disableFocusListener content={I18n.t('add_link')}>
|
||||
<UIButton
|
||||
onClick={togglePopoverVisible}
|
||||
{...iconButtonProps}
|
||||
className={classNames(
|
||||
visible && styles['icon-button-active'],
|
||||
styles['icon-button'],
|
||||
)}
|
||||
icon={<IconLinkStroked />}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</InsertLinkPopover>
|
||||
|
||||
{/* 暂时禁用,后续放开 */}
|
||||
{SHOW_ADD_NAME_BTN && (
|
||||
<Tooltip content={I18n.t('add_nickname')}>
|
||||
<UIButton
|
||||
{...iconButtonProps}
|
||||
icon={<IconMemberOutlined />}
|
||||
disabled={disabled}
|
||||
onClick={onInsertVariable}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
.popover-content {
|
||||
display: flex;
|
||||
column-gap: 12px;
|
||||
justify-content: space-between;
|
||||
|
||||
box-sizing: content-box;
|
||||
width: 398px;
|
||||
height: 128px;
|
||||
padding: 24px;
|
||||
|
||||
// rgb mark
|
||||
background: #F4F4F6;
|
||||
|
||||
|
||||
.input-content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
row-gap: 16px;
|
||||
}
|
||||
|
||||
.confirm-button {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 { useState, type ComponentProps, type PropsWithChildren } from 'react';
|
||||
|
||||
import { Form, Popover, UIButton, UIInput } from '@coze-arch/bot-semi';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface InsertLinkPopoverProps
|
||||
extends Pick<ComponentProps<typeof Popover>, 'onClickOutSide' | 'visible'> {
|
||||
onConfirm?: (param: { link: string; text: string }) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全受控
|
||||
*/
|
||||
export const InsertLinkPopover: React.FC<
|
||||
PropsWithChildren<InsertLinkPopoverProps>
|
||||
> = ({ children, visible, onClickOutSide, onConfirm }) => (
|
||||
<Popover
|
||||
trigger="custom"
|
||||
visible={visible}
|
||||
onClickOutSide={onClickOutSide}
|
||||
showArrow={false}
|
||||
position="topRight"
|
||||
content={<Content onConfirm={onConfirm} />}
|
||||
>
|
||||
{children}
|
||||
</Popover>
|
||||
);
|
||||
|
||||
const Content: React.FC<Pick<InsertLinkPopoverProps, 'onConfirm'>> = ({
|
||||
onConfirm: inputOnConfirm,
|
||||
}) => {
|
||||
const [text, setText] = useState('');
|
||||
const [link, setLink] = useState('');
|
||||
const onConfirm = () => {
|
||||
clearInput();
|
||||
inputOnConfirm?.({ text, link });
|
||||
};
|
||||
const clearInput = () => {
|
||||
setLink('');
|
||||
setText('');
|
||||
};
|
||||
return (
|
||||
<div className={styles['popover-content']}>
|
||||
<div className={styles['input-content']}>
|
||||
<div className={styles['input-row']}>
|
||||
<Form.Label required text="Text" />
|
||||
<UIInput value={text} onChange={setText} />
|
||||
</div>
|
||||
<div className={styles['input-row']}>
|
||||
<Form.Label required text="Link" />
|
||||
<UIInput value={link} onChange={setLink} />
|
||||
</div>
|
||||
</div>
|
||||
<UIButton
|
||||
onClick={onConfirm}
|
||||
disabled={!text || !link}
|
||||
theme="solid"
|
||||
className={styles['confirm-button']}
|
||||
>
|
||||
Confirm
|
||||
</UIButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
.mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 16px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background: rgb(255 255 255 / 90%);
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 14px;
|
||||
color: rgba(6, 7, 9, 50%);
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 250px;
|
||||
height: 6px;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 { I18n } from '@coze-arch/i18n';
|
||||
import { Progress } from '@coze-arch/bot-semi';
|
||||
|
||||
import { type UploadState } from '../../type';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const UploadProgressMask: React.FC<UploadState> = ({
|
||||
fileName,
|
||||
percent,
|
||||
}) => (
|
||||
<div className={styles.mask}>
|
||||
<div className={styles.text}>
|
||||
{I18n.t('uploading_filename', { filename: fileName })}
|
||||
</div>
|
||||
<Progress className={styles.progress} percent={percent} />
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 { I18n } from '@coze-arch/i18n';
|
||||
|
||||
export const MAX_FILE_SIZE = 20 * 1024 * 1024;
|
||||
export const getFileSizeReachLimitI18n = () =>
|
||||
I18n.t('file_too_large', {
|
||||
max_size: '20MB',
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export enum OnboardingVariable {
|
||||
USER_NAME = 'user_name',
|
||||
}
|
||||
|
||||
export type OnboardingVariableMap = Record<OnboardingVariable, string>;
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const getInsertTextAtPosition = ({
|
||||
text,
|
||||
insertText,
|
||||
position,
|
||||
}: {
|
||||
text: string;
|
||||
insertText: string;
|
||||
position: number;
|
||||
}): string => `${text.slice(0, position)}${insertText}${text.slice(position)}`;
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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 { primitiveExhaustiveCheck } from '../utils/exhaustive-check';
|
||||
import { type SyncAction } from '../type';
|
||||
import { getMarkdownLink } from './get-markdown-link';
|
||||
|
||||
export const getSyncInsertText = (action: SyncAction): string => {
|
||||
const { type, payload } = action;
|
||||
if (type === 'link') {
|
||||
const { text, link } = payload;
|
||||
return getMarkdownLink({ text, link });
|
||||
}
|
||||
if (type === 'variable') {
|
||||
return payload.variableTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 不应该走到这里
|
||||
*/
|
||||
primitiveExhaustiveCheck(type);
|
||||
return '';
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const getIsFileFormatValid = (file: File) =>
|
||||
file.type.startsWith('image/') && file.type !== 'image/svg+xml';
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const getMarkdownImageLink = ({
|
||||
fileName,
|
||||
link,
|
||||
}: {
|
||||
fileName: string;
|
||||
link: string;
|
||||
}) => ``;
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const getMarkdownLink = ({
|
||||
text,
|
||||
link,
|
||||
}: {
|
||||
text: string;
|
||||
link: string;
|
||||
}) => `[${text}](${link})`;
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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 {
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
type ChangeEventHandler,
|
||||
} from 'react';
|
||||
|
||||
import useEventCallback from 'use-event-callback';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useDragAndPasteUpload } from '@coze-arch/bot-hooks';
|
||||
|
||||
import { primitiveExhaustiveCheck } from '../utils/exhaustive-check';
|
||||
import { type AsyncAction, type SyncAction } from '../type';
|
||||
import { getMarkdownImageLink } from '../helpers/get-markdown-image-link';
|
||||
import { getIsFileFormatValid } from '../helpers/get-is-file-format-valid';
|
||||
import { getInsertTextAtPosition } from '../helpers/get-insert-text-at-position';
|
||||
import { getSyncInsertText } from '../helpers/get-insert-text';
|
||||
import { MAX_FILE_SIZE, getFileSizeReachLimitI18n } from '../constant/file';
|
||||
import { type ActionBarProps } from '../components/action-bar';
|
||||
import { type MarkdownEditorProps } from '..';
|
||||
import { useUpload } from './use-upload-file';
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
export const useMarkdownEditor = ({
|
||||
value,
|
||||
onChange,
|
||||
getUserId = () => '',
|
||||
customUpload,
|
||||
}: MarkdownEditorProps) => {
|
||||
const onTriggerSyncAction = (action: SyncAction) => {
|
||||
handleInsertText(getSyncInsertText(action));
|
||||
};
|
||||
|
||||
const onTriggerAsyncAction = (action: AsyncAction) => {
|
||||
const { type, payload } = action;
|
||||
if (type === 'image') {
|
||||
const { file } = payload;
|
||||
return uploadFileList([file]);
|
||||
}
|
||||
|
||||
primitiveExhaustiveCheck(type);
|
||||
};
|
||||
|
||||
const onTriggerAction: ActionBarProps['onTriggerAction'] = props => {
|
||||
if (props.sync) {
|
||||
return onTriggerSyncAction(props);
|
||||
}
|
||||
return onTriggerAsyncAction(props);
|
||||
};
|
||||
|
||||
const ref = useRef<HTMLTextAreaElement>(null);
|
||||
const dragTargetRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onUploadAllSuccess = useEventCallback(
|
||||
({ url, fileName }: { url: string; fileName: string }) => {
|
||||
handleInsertText(
|
||||
getMarkdownImageLink({
|
||||
fileName,
|
||||
link: url,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// 判断使用内置上传方法 or 自定义
|
||||
const selectUploadMethod = () => {
|
||||
if (customUpload) {
|
||||
return customUpload({
|
||||
onUploadAllSuccess,
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks -- linter-disable-autofix
|
||||
return useUpload({
|
||||
getUserId,
|
||||
onUploadAllSuccess,
|
||||
});
|
||||
}
|
||||
};
|
||||
const { uploadFileList, uploadState } = selectUploadMethod();
|
||||
|
||||
const { isDragOver } = useDragAndPasteUpload({
|
||||
ref: dragTargetRef,
|
||||
onUpload: fileList => {
|
||||
const file = fileList.at(0);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
onTriggerAction({ type: 'image', sync: false, payload: { file } });
|
||||
},
|
||||
disableDrag: Boolean(uploadState),
|
||||
disablePaste: Boolean(uploadState),
|
||||
fileLimit: 1,
|
||||
maxFileSize: MAX_FILE_SIZE,
|
||||
isFileFormatValid: getIsFileFormatValid,
|
||||
getExistingFileCount: () => 0,
|
||||
closeDelay: undefined,
|
||||
invalidFormatMessage: I18n.t('file_format_not_supported'),
|
||||
invalidSizeMessage: getFileSizeReachLimitI18n(),
|
||||
fileExceedsMessage: I18n.t('files_exceeds_limit'),
|
||||
});
|
||||
|
||||
const [wrapInsertionIndex, setWrapInsertionIndex] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (wrapInsertionIndex === null || !ref.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.current.selectionStart = wrapInsertionIndex;
|
||||
ref.current.selectionEnd = wrapInsertionIndex;
|
||||
setWrapInsertionIndex(null);
|
||||
}, [ref.current, wrapInsertionIndex, value]);
|
||||
console.log('outter value', { value });
|
||||
|
||||
const onTextareaChange: ChangeEventHandler<HTMLTextAreaElement> = e => {
|
||||
console.log('onTextareaChange', { value: e.target.value });
|
||||
|
||||
onChange(e.target.value);
|
||||
};
|
||||
|
||||
const handleInsertText = (insertText: string) => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
ref.current.focus();
|
||||
const { selectionEnd } = ref.current;
|
||||
/**
|
||||
* 选中文字时点击 action bar, 将内容插入到文字的末尾
|
||||
*/
|
||||
console.log('handleInsertText', { value, insertText, selectionEnd });
|
||||
|
||||
const insertTextAtPosition = getInsertTextAtPosition({
|
||||
text: value,
|
||||
insertText,
|
||||
position: selectionEnd,
|
||||
});
|
||||
onChange(insertTextAtPosition);
|
||||
setWrapInsertionIndex(selectionEnd + insertText.length);
|
||||
};
|
||||
|
||||
return {
|
||||
textAreaRef: ref,
|
||||
onTextareaChange,
|
||||
onTriggerAction,
|
||||
dragTargetRef,
|
||||
uploadState,
|
||||
isDragOver,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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 { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import { withSlardarIdButton } from '@coze-studio/bot-utils';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Toast } from '@coze-arch/bot-semi';
|
||||
|
||||
import { type UploadState } from '../type';
|
||||
import { UploadController } from '../service/upload-controller';
|
||||
|
||||
/**
|
||||
* 暂时没有场景,所以这里将多实例、一次行上传多文件的能力屏蔽了
|
||||
*/
|
||||
export const useUpload = ({
|
||||
getUserId,
|
||||
onUploadAllSuccess,
|
||||
}: {
|
||||
getUserId: () => string;
|
||||
onUploadAllSuccess: (param: { url: string; fileName: string }) => void;
|
||||
}) => {
|
||||
const [uploadState, setUploadState] = useState<UploadState | null>(null);
|
||||
|
||||
const uploadControllerMap = useRef<Record<string, UploadController>>({});
|
||||
|
||||
const clearState = () => setUploadState(null);
|
||||
|
||||
const deleteUploadControllerById = (id: string) => {
|
||||
delete uploadControllerMap.current[id];
|
||||
};
|
||||
const cancelUploadById = (id: string) => {
|
||||
const controller = uploadControllerMap.current[id];
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
controller.cancel();
|
||||
deleteUploadControllerById(id);
|
||||
};
|
||||
|
||||
const handleError = (_e: unknown, controllerId: string) => {
|
||||
clearState();
|
||||
cancelUploadById(controllerId);
|
||||
Toast.error({
|
||||
content: withSlardarIdButton(I18n.t('Upload_failed')),
|
||||
showClose: false,
|
||||
});
|
||||
};
|
||||
|
||||
const handleUploadSuccess = () => {
|
||||
setUploadState(null);
|
||||
};
|
||||
|
||||
const handleProgress = (percent: number) => {
|
||||
setUploadState(state => {
|
||||
if (!state) {
|
||||
return state;
|
||||
}
|
||||
return { ...state, percent };
|
||||
});
|
||||
};
|
||||
|
||||
const handleStartUpload = (fileName: string) =>
|
||||
setUploadState({ fileName, percent: 0 });
|
||||
|
||||
const uploadFileList = (fileList: File[]) => {
|
||||
if (uploadState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const controllerId = nanoid();
|
||||
|
||||
const file = fileList.at(0);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
handleStartUpload(file.name);
|
||||
|
||||
uploadControllerMap.current[controllerId] = new UploadController({
|
||||
fileList,
|
||||
controllerId,
|
||||
userId: getUserId(),
|
||||
onProgress: event => {
|
||||
handleProgress(event.percent);
|
||||
},
|
||||
onComplete: event => {
|
||||
handleUploadSuccess();
|
||||
onUploadAllSuccess(event);
|
||||
},
|
||||
onUploadError: handleError,
|
||||
onGetTokenError: handleError,
|
||||
onGetUploadInstanceError: handleError,
|
||||
});
|
||||
};
|
||||
|
||||
const clearAllSideEffect = () => {
|
||||
Object.entries(uploadControllerMap.current).forEach(([, controller]) =>
|
||||
controller.cancel(),
|
||||
);
|
||||
uploadControllerMap.current = {};
|
||||
};
|
||||
|
||||
useEffect(() => clearAllSideEffect, []);
|
||||
|
||||
return {
|
||||
uploadState,
|
||||
uploadFileList,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
.markdown-editor {
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
padding: 6px 12px;
|
||||
|
||||
background-color: #fff;
|
||||
// rgb mark
|
||||
border: 1px solid;
|
||||
border-color: rgb(6 7 9 / 10%);
|
||||
border-radius: 8px;
|
||||
|
||||
&.markdown-editor-drag {
|
||||
box-shadow: 0 0 0 2px #DADCFB;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
border-color: #34F;
|
||||
}
|
||||
|
||||
.markdown-action-bar {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px 0;
|
||||
|
||||
border-bottom: 1px solid rgb(6 7 9 / 10%);
|
||||
}
|
||||
|
||||
.markdown-editor-content {
|
||||
resize: none;
|
||||
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
padding: 0;
|
||||
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
|
||||
// rgb mark
|
||||
color: #383743;
|
||||
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
&::selection {
|
||||
background: rgb(77 83 232 / 20%);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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 } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { type CustomUploadParams, type CustomUploadRes } from './type';
|
||||
import { useMarkdownEditor } from './hooks/use-markdown-editor';
|
||||
import { UploadProgressMask } from './components/upload-progress-mask';
|
||||
import { ActionBar } from './components/action-bar';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface MarkdownEditorProps {
|
||||
value: string;
|
||||
placeholder?: string;
|
||||
onChange: (value: string) => void;
|
||||
getUserId?: () => string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
style?: CSSProperties;
|
||||
customUpload?: (params: CustomUploadParams) => CustomUploadRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全受控组件
|
||||
*/
|
||||
export const MarkdownEditor: React.FC<MarkdownEditorProps> = ({
|
||||
value = '',
|
||||
placeholder = '',
|
||||
className,
|
||||
disabled,
|
||||
style,
|
||||
...props
|
||||
}) => {
|
||||
const {
|
||||
textAreaRef,
|
||||
dragTargetRef,
|
||||
onTextareaChange,
|
||||
onTriggerAction,
|
||||
isDragOver,
|
||||
uploadState,
|
||||
} = useMarkdownEditor({
|
||||
value,
|
||||
...props,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
styles['markdown-editor'],
|
||||
isDragOver && styles['markdown-editor-drag'],
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
ref={dragTargetRef}
|
||||
>
|
||||
<ActionBar
|
||||
className={styles['markdown-action-bar']}
|
||||
onTriggerAction={onTriggerAction}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<textarea
|
||||
ref={textAreaRef}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
onChange={onTextareaChange}
|
||||
className={styles['markdown-editor-content']}
|
||||
/>
|
||||
{uploadState && <UploadProgressMask {...uploadState} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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 {
|
||||
uploadFileV2,
|
||||
type EventPayloadMaps as BaseEventPayloadMap,
|
||||
type UploaderInstance,
|
||||
type UploadFileV2Param,
|
||||
type FileItem,
|
||||
} from '@coze-arch/bot-utils/upload-file-v2';
|
||||
import { PlaygroundApi } from '@coze-arch/bot-api';
|
||||
|
||||
export type EventPayloadMap = BaseEventPayloadMap & {
|
||||
ready: boolean;
|
||||
};
|
||||
|
||||
export interface UploadControllerProps {
|
||||
controllerId: string;
|
||||
fileList: File[];
|
||||
userId: string;
|
||||
onProgress?: (
|
||||
event: EventPayloadMap['progress'],
|
||||
controllerId: string,
|
||||
) => void;
|
||||
onComplete?: (
|
||||
event: {
|
||||
url: string;
|
||||
fileName: string;
|
||||
},
|
||||
controllerId: string,
|
||||
) => void;
|
||||
onUploadError?: (event: Error, controllerId: string) => void;
|
||||
onUploaderReady?: (
|
||||
event: EventPayloadMap['ready'],
|
||||
controllerId: string,
|
||||
) => void;
|
||||
onStartUpload?: (
|
||||
param: Parameters<Required<UploadFileV2Param>['onStartUpload']>[number],
|
||||
controllerId: string,
|
||||
) => void;
|
||||
onGetUploadInstanceError?: (error: Error, controllerId: string) => void;
|
||||
onGetTokenError?: (error: Error, controllerId: string) => void;
|
||||
}
|
||||
|
||||
const isImage = (file: File) => file.type.startsWith('image/');
|
||||
|
||||
export class UploadController {
|
||||
controllerId: string;
|
||||
abortController: AbortController;
|
||||
uploader: UploaderInstance | null;
|
||||
fileItemList: FileItem[];
|
||||
|
||||
constructor({
|
||||
controllerId,
|
||||
fileList,
|
||||
userId,
|
||||
onProgress,
|
||||
onComplete,
|
||||
onUploadError,
|
||||
onUploaderReady,
|
||||
onStartUpload,
|
||||
onGetTokenError,
|
||||
onGetUploadInstanceError,
|
||||
}: UploadControllerProps) {
|
||||
this.fileItemList = fileList.map(file => ({
|
||||
file,
|
||||
fileType: isImage(file) ? 'image' : 'object',
|
||||
}));
|
||||
this.controllerId = controllerId;
|
||||
this.abortController = new AbortController();
|
||||
this.uploader = null;
|
||||
uploadFileV2({
|
||||
fileItemList: this.fileItemList,
|
||||
userId,
|
||||
signal: this.abortController.signal,
|
||||
timeout: undefined,
|
||||
onUploaderReady: uploader => {
|
||||
this.uploader = uploader;
|
||||
onUploaderReady?.(true, controllerId);
|
||||
},
|
||||
onProgress: event => onProgress?.(event, controllerId),
|
||||
onSuccess: async event => {
|
||||
const uri = event.uploadResult.Uri;
|
||||
try {
|
||||
if (!uri) {
|
||||
throw new Error(
|
||||
`upload success without uri, uploadID ${event.uploadID}`,
|
||||
);
|
||||
}
|
||||
|
||||
const result = await PlaygroundApi.GetImagexShortUrl({
|
||||
uris: [uri],
|
||||
});
|
||||
|
||||
const url = result.data?.url_info?.[uri]?.url;
|
||||
|
||||
if (!url) {
|
||||
throw new Error(`failed to get url, uri: ${uri}`);
|
||||
}
|
||||
|
||||
onComplete?.(
|
||||
{ url, fileName: event.uploadResult.FileName ?? '' },
|
||||
controllerId,
|
||||
);
|
||||
} catch (e) {
|
||||
onUploadError?.(
|
||||
e instanceof Error ? e : new Error(String(e)),
|
||||
controllerId,
|
||||
);
|
||||
}
|
||||
},
|
||||
onUploadError: event => onUploadError?.(event.extra.error, controllerId),
|
||||
onStartUpload: event => onStartUpload?.(event, controllerId),
|
||||
onGetUploadInstanceError: error =>
|
||||
onGetUploadInstanceError?.(error, controllerId),
|
||||
onGetTokenError: error => onGetTokenError?.(error, controllerId),
|
||||
});
|
||||
}
|
||||
|
||||
cancel = () => {
|
||||
this.abortController.abort();
|
||||
};
|
||||
|
||||
pause = () => {
|
||||
this.uploader?.pause();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface InsertImageAction {
|
||||
type: 'image';
|
||||
sync: false;
|
||||
payload: {
|
||||
file: File;
|
||||
};
|
||||
}
|
||||
|
||||
export interface InsertLinkAction {
|
||||
type: 'link';
|
||||
sync: true;
|
||||
payload: {
|
||||
text: string;
|
||||
link: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface InsertVariableAction {
|
||||
type: 'variable';
|
||||
sync: true;
|
||||
payload: {
|
||||
variableTemplate: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type SyncAction = InsertLinkAction | InsertVariableAction;
|
||||
|
||||
export type AsyncAction = InsertImageAction;
|
||||
|
||||
export type TriggerAction = SyncAction | AsyncAction;
|
||||
|
||||
export interface UploadState {
|
||||
percent: number;
|
||||
fileName: string;
|
||||
}
|
||||
|
||||
// 自定义上传方法入参
|
||||
export interface CustomUploadParams {
|
||||
onUploadAllSuccess: (param: { url: string; fileName: string }) => void;
|
||||
}
|
||||
// 自定义上传方法出参
|
||||
export interface CustomUploadRes {
|
||||
uploadFileList: (fileList: File[]) => void;
|
||||
//null表示已完成
|
||||
uploadState: UploadState | null;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const primitiveExhaustiveCheck = (_: never) => 0;
|
||||
export const recordExhaustiveCheck = (_: Record<string, never>) => 0;
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 OnboardingVariable,
|
||||
type OnboardingVariableMap,
|
||||
} from '../constant/onboarding-variable';
|
||||
import { typedKeys } from './typed-keys';
|
||||
|
||||
export interface VariableWithRange {
|
||||
range: [number, number];
|
||||
variable: OnboardingVariable;
|
||||
}
|
||||
|
||||
export const getFixedVariableTemplate = (template: string) => `{{${template}}}`;
|
||||
|
||||
export const matchAllTemplateRanges = (
|
||||
text: string,
|
||||
template: string,
|
||||
): { start: number; end: number }[] => {
|
||||
// 正则表达式,用于匹配双花括号内的内容
|
||||
const templateRegex = new RegExp(getFixedVariableTemplate(template), 'g');
|
||||
const matches: { start: number; end: number }[] = [];
|
||||
|
||||
// 循环查找所有匹配项
|
||||
while (true) {
|
||||
const match = templateRegex.exec(text);
|
||||
|
||||
if (!match) {
|
||||
break;
|
||||
}
|
||||
const templateString = match[0];
|
||||
const start = match.index;
|
||||
const end = templateString.length + start;
|
||||
|
||||
matches.push({ start, end });
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
|
||||
export const getVariableRangeList = (
|
||||
content: string,
|
||||
variableMap: OnboardingVariableMap,
|
||||
) => {
|
||||
const result: VariableWithRange[] = [];
|
||||
typedKeys(variableMap).forEach(variable => {
|
||||
const allMatchedRanges = matchAllTemplateRanges(content, variable);
|
||||
const variableWithRangeList: VariableWithRange[] = allMatchedRanges.map(
|
||||
({ start, end }) => ({
|
||||
variable,
|
||||
range: [start, end],
|
||||
}),
|
||||
);
|
||||
result.push(...variableWithRangeList);
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export const typedKeys = <T extends Parameters<typeof Object.keys>[number]>(
|
||||
o: T,
|
||||
): Array<keyof T> => Object.keys(o) as Array<keyof T>;
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 { MdBoxLazy } from '@coze-arch/bot-md-box-adapter/lazy';
|
||||
import { type ModelDescGroup } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
export const ModelDescription: React.FC<{
|
||||
descriptionGroupList: ModelDescGroup[];
|
||||
}> = ({ descriptionGroupList }) => (
|
||||
<MdBoxLazy
|
||||
autoFixSyntax={{ autoFixEnding: false }}
|
||||
markDown={descriptionGroupList
|
||||
.map(({ group_name, desc }) => `${group_name}\n${desc?.join('\n')}`)
|
||||
.join('\n\n')}
|
||||
/>
|
||||
);
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* 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 { lazy, Suspense } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
Tag,
|
||||
Highlight,
|
||||
Typography,
|
||||
Avatar,
|
||||
type TagProps,
|
||||
} from '@coze-arch/coze-design';
|
||||
import { Popover } from '@coze-arch/bot-semi';
|
||||
import { IconInfo } from '@coze-arch/bot-icons';
|
||||
import { useFlags } from '@coze-arch/bot-flags';
|
||||
import { type ModelDescGroup } from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
const LazyModelDescription = lazy(async () => {
|
||||
const { ModelDescription } = await import('./model-description');
|
||||
return {
|
||||
default: ModelDescription,
|
||||
};
|
||||
});
|
||||
const ModelDescription = (props: {
|
||||
descriptionGroupList: ModelDescGroup[];
|
||||
}) => (
|
||||
<Suspense>
|
||||
<LazyModelDescription {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
export interface OptionItemTag {
|
||||
label: string;
|
||||
color?: TagProps['color'];
|
||||
}
|
||||
export interface OptionItemProps {
|
||||
tokenLimit: number | undefined;
|
||||
descriptionGroupList: ModelDescGroup[] | undefined;
|
||||
avatar: string | undefined;
|
||||
name: string | undefined;
|
||||
searchWords?: string[];
|
||||
endPointName?: string; // 接入点名称(专业版有)
|
||||
showEndPointName?: boolean;
|
||||
className?: string;
|
||||
/**
|
||||
* @deprecated
|
||||
* 原先只会有「限额」标签,M-5395720900 后会有大量新标签,避免兼容问题产品同意先简单隐藏掉标签展示
|
||||
*/
|
||||
tags?: OptionItemTag[];
|
||||
}
|
||||
|
||||
export const ModelOptionItem: React.FC<OptionItemProps> = ({
|
||||
avatar,
|
||||
descriptionGroupList,
|
||||
tokenLimit = 0,
|
||||
name,
|
||||
searchWords = [],
|
||||
endPointName,
|
||||
showEndPointName = false,
|
||||
className,
|
||||
}) => {
|
||||
const [FLAGS] = useFlags();
|
||||
const tags: OptionItemTag[] = [];
|
||||
|
||||
const shouldShowEndPoint = showEndPointName && endPointName;
|
||||
// 社区版暂不支持该功能
|
||||
const displayName = FLAGS['bot.studio.model_select_switch_end_point_name_pos']
|
||||
? endPointName || name
|
||||
: name;
|
||||
|
||||
// 社区版暂不支持该功能
|
||||
const displayEndPointName = FLAGS[
|
||||
'bot.studio.model_select_switch_end_point_name_pos'
|
||||
]
|
||||
? name
|
||||
: endPointName;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'w-full px-[8px] flex justify-between overflow-hidden gap-[16px]',
|
||||
{
|
||||
'py-2': showEndPointName,
|
||||
},
|
||||
className,
|
||||
endPointName ? 'items-start' : 'items-center',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="flex-1 flex items-center gap-[8px] overflow-hidden"
|
||||
data-testid="bot.ide.bot_creator.select_model_formitem"
|
||||
>
|
||||
<Avatar
|
||||
shape="square"
|
||||
src={avatar}
|
||||
className={classNames('shrink-0', {
|
||||
'!h-4 !w-4': !showEndPointName,
|
||||
'!h-8 !w-8': showEndPointName,
|
||||
})}
|
||||
data-testid="bot-detail.model-config-modal.model-avatar"
|
||||
/>
|
||||
<div
|
||||
className={classNames('flex-1', {
|
||||
'items-center': showEndPointName && !endPointName,
|
||||
})}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span
|
||||
className="inline-block truncate leading-[20px]"
|
||||
data-testid="bot-detail.model-config-modal.model-name"
|
||||
>
|
||||
<Highlight
|
||||
sourceString={displayName}
|
||||
searchWords={searchWords}
|
||||
highlightClassName="coz-fg-hglt-yellow bg-transparent"
|
||||
/>
|
||||
</span>
|
||||
{descriptionGroupList?.length ? (
|
||||
<Popover
|
||||
trigger="hover"
|
||||
className="max-w-[224px] py-[8px] px-[12px]"
|
||||
content={
|
||||
<ModelDescription
|
||||
descriptionGroupList={descriptionGroupList}
|
||||
data-testid="bot-detail.model-config-modal-model.description-popover"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<IconInfo
|
||||
data-testid="bot-detail.model-config-modal.model-info-button"
|
||||
className="ml-[4px]"
|
||||
/>
|
||||
</Popover>
|
||||
) : null}
|
||||
<Tag
|
||||
prefixIcon={null}
|
||||
color="primary"
|
||||
className="shrink-0 !ml-[8px]"
|
||||
data-testid="bot-detail.model-config-modal.model-token-tag"
|
||||
size="mini"
|
||||
>
|
||||
{(tokenLimit / 1024).toFixed(0)}K
|
||||
</Tag>
|
||||
</div>
|
||||
{shouldShowEndPoint ? (
|
||||
<Typography.Text
|
||||
className="coz-fg-secondary text-[12px] leading-[16px]"
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: {
|
||||
content: displayEndPointName,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Highlight
|
||||
sourceString={displayEndPointName}
|
||||
searchWords={searchWords}
|
||||
highlightClassName="coz-fg-hglt-yellow bg-transparent"
|
||||
component="span"
|
||||
/>
|
||||
</Typography.Text>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{tags?.length ? (
|
||||
<div
|
||||
className={classNames('flex shrink-0', {
|
||||
'pt-[2px]': shouldShowEndPoint,
|
||||
})}
|
||||
>
|
||||
{tags.map(tag => (
|
||||
<Tag color={tag.color} key={tag.label} size="mini">
|
||||
{tag.label}
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
25
frontend/packages/studio/components/src/monetize/index.ts
Normal file
25
frontend/packages/studio/components/src/monetize/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export {
|
||||
MonetizeConfigPanel,
|
||||
MonetizeConfigValue,
|
||||
} from './monetize-config-panel';
|
||||
export { MonetizeCreditRefreshCycle } from './monetize-credit-refresh-cycle';
|
||||
export { MonetizeDescription } from './monetize-description';
|
||||
export { MonetizeFreeChatCount } from './monetize-free-chat-count';
|
||||
export { MonetizeSwitch } from './monetize-switch';
|
||||
export { MonetizePublishInfo } from './monetize-publish-info';
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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 { useDebounceFn } from 'ahooks';
|
||||
import { type BotMonetizationRefreshPeriod } from '@coze-arch/bot-api/benefit';
|
||||
|
||||
import { MonetizeSwitch } from '../monetize-switch';
|
||||
import { MonetizeFreeChatCount } from '../monetize-free-chat-count';
|
||||
import { MonetizeDescription } from '../monetize-description';
|
||||
import { MonetizeCreditRefreshCycle } from '../monetize-credit-refresh-cycle';
|
||||
|
||||
export interface MonetizeConfigValue {
|
||||
/** 是否开启付费 */
|
||||
isOn: boolean;
|
||||
/** 开启付费后,用户免费体验的次数 */
|
||||
freeCount: number;
|
||||
/** 刷新周期 */
|
||||
refreshCycle: BotMonetizationRefreshPeriod;
|
||||
}
|
||||
|
||||
interface MonetizeConfigPanelProps {
|
||||
disabled?: boolean;
|
||||
value: MonetizeConfigValue;
|
||||
onChange: (value: MonetizeConfigValue) => void;
|
||||
/**
|
||||
* 内置防抖后的 onChange 事件,业务侧可选择性使用,正常只传 onChange 即可
|
||||
* (由于该组件是完全受控组件,因此不能只传 onDebouncedChange,必须传 onChange 实时更新视图)
|
||||
*/
|
||||
onDebouncedChange?: (value: MonetizeConfigValue) => void;
|
||||
}
|
||||
|
||||
export function MonetizeConfigPanel({
|
||||
disabled = false,
|
||||
value,
|
||||
onChange,
|
||||
onDebouncedChange,
|
||||
}: MonetizeConfigPanelProps) {
|
||||
const { run: debouncedChange } = useDebounceFn(
|
||||
({ isOn, freeCount, refreshCycle }: MonetizeConfigValue) => {
|
||||
onDebouncedChange?.({
|
||||
isOn,
|
||||
freeCount,
|
||||
refreshCycle,
|
||||
});
|
||||
},
|
||||
{ wait: 300 },
|
||||
);
|
||||
|
||||
const refreshCycleDisabled = !value.isOn || disabled || value.freeCount <= 0;
|
||||
|
||||
return (
|
||||
<div className="w-[480px] p-[24px] flex flex-col gap-[24px]">
|
||||
<MonetizeSwitch
|
||||
disabled={disabled}
|
||||
isOn={value.isOn}
|
||||
onChange={v => {
|
||||
onChange({ ...value, isOn: v });
|
||||
debouncedChange({
|
||||
...value,
|
||||
isOn: v,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<MonetizeDescription isOn={value.isOn} />
|
||||
<MonetizeFreeChatCount
|
||||
isOn={value.isOn}
|
||||
disabled={disabled}
|
||||
freeCount={value.freeCount}
|
||||
onFreeCountChange={v => {
|
||||
onChange({ ...value, freeCount: v });
|
||||
debouncedChange({
|
||||
...value,
|
||||
freeCount: v,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<MonetizeCreditRefreshCycle
|
||||
freeCount={value.freeCount}
|
||||
disabled={refreshCycleDisabled}
|
||||
refreshCycle={value.refreshCycle}
|
||||
onRefreshCycleChange={v => {
|
||||
onChange({ ...value, refreshCycle: v });
|
||||
debouncedChange({ ...value, refreshCycle: v });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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 { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozInfoCircle } from '@coze-arch/coze-design/icons';
|
||||
import { Tooltip, Select } from '@coze-arch/coze-design';
|
||||
import { BotMonetizationRefreshPeriod } from '@coze-arch/bot-api/benefit';
|
||||
|
||||
const refreshCycleTextMap: Record<BotMonetizationRefreshPeriod, string> = {
|
||||
[BotMonetizationRefreshPeriod.Unknown]: I18n.t(
|
||||
'coze_premium_credits_cycle_4',
|
||||
),
|
||||
[BotMonetizationRefreshPeriod.Never]: I18n.t('coze_premium_credits_cycle_4'),
|
||||
[BotMonetizationRefreshPeriod.Day]: I18n.t('coze_premium_credits_cycle_1'),
|
||||
[BotMonetizationRefreshPeriod.Week]: I18n.t('coze_premium_credits_cycle_2'),
|
||||
[BotMonetizationRefreshPeriod.Month]: I18n.t('coze_premium_credits_cycle_3'),
|
||||
};
|
||||
|
||||
const getOptionList = () => [
|
||||
{
|
||||
value: BotMonetizationRefreshPeriod.Never,
|
||||
text: refreshCycleTextMap[BotMonetizationRefreshPeriod.Never],
|
||||
},
|
||||
{
|
||||
value: BotMonetizationRefreshPeriod.Day,
|
||||
text: refreshCycleTextMap[BotMonetizationRefreshPeriod.Day],
|
||||
tooltip: I18n.t('coze_premium_credits_cycle_tip6'),
|
||||
},
|
||||
{
|
||||
value: BotMonetizationRefreshPeriod.Week,
|
||||
text: refreshCycleTextMap[BotMonetizationRefreshPeriod.Week],
|
||||
tooltip: I18n.t('coze_premium_credits_cycle_tip7'),
|
||||
},
|
||||
{
|
||||
value: BotMonetizationRefreshPeriod.Month,
|
||||
text: refreshCycleTextMap[BotMonetizationRefreshPeriod.Month],
|
||||
tooltip: I18n.t('coze_premium_credits_cycle_tip8'),
|
||||
},
|
||||
];
|
||||
|
||||
export function MonetizeCreditRefreshCycle({
|
||||
refreshCycle,
|
||||
onRefreshCycleChange,
|
||||
disabled,
|
||||
freeCount,
|
||||
}: {
|
||||
freeCount: number;
|
||||
disabled: boolean;
|
||||
refreshCycle: number;
|
||||
onRefreshCycleChange: (value: number) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4px">
|
||||
<div className="coz-fg-primary text-lg font-medium">
|
||||
{I18n.t('coze_premium_credits_cycle_5')}
|
||||
</div>
|
||||
<Tooltip
|
||||
theme="dark"
|
||||
content={I18n.t('coze_premium_credits_cycle_tip1')}
|
||||
>
|
||||
<IconCozInfoCircle className="text-base coz-fg-secondary" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
key={freeCount}
|
||||
trigger={freeCount <= 0 ? 'hover' : 'custom'}
|
||||
content={I18n.t('coze_premium_credits_cycle_tip4')}
|
||||
>
|
||||
<Select
|
||||
disabled={disabled}
|
||||
onChange={value => {
|
||||
onRefreshCycleChange(Number(value));
|
||||
}}
|
||||
value={refreshCycle}
|
||||
position="bottomRight"
|
||||
className="w-[140px]"
|
||||
renderSelectedItem={(item: Record<string, unknown>) =>
|
||||
refreshCycleTextMap[item.value as BotMonetizationRefreshPeriod]
|
||||
}
|
||||
>
|
||||
{getOptionList().map(item => (
|
||||
<Select.Option key={item.value} value={item.value}>
|
||||
<div className="mx-8px w-[100px]">{item.text}</div>
|
||||
{item.tooltip ? (
|
||||
<Tooltip theme="dark" position="right" content={item.tooltip}>
|
||||
<IconCozInfoCircle className="mr-8px coz-fg-secondary text-base" />
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 { I18n } from '@coze-arch/i18n';
|
||||
import { Popover } from '@coze-arch/coze-design';
|
||||
|
||||
import previewCard from './preview-card.png';
|
||||
|
||||
export function MonetizeDescription({ isOn }: { isOn: boolean }) {
|
||||
return (
|
||||
<div className="coz-fg-primary">
|
||||
<span>
|
||||
{isOn ? I18n.t('monetization_on_des') : I18n.t('monetization_off_des')}
|
||||
</span>
|
||||
{isOn ? (
|
||||
<Popover
|
||||
content={
|
||||
<div className="p-[12px] coz-bg-max rounded-[10px]">
|
||||
<img width={320} src={previewCard} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<span className="coz-fg-hglt cursor-pointer">
|
||||
{I18n.t('monetization_on_viewbill')}
|
||||
</span>
|
||||
</Popover>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozInfoCircle } from '@coze-arch/coze-design/icons';
|
||||
import { InputNumber, Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
export function MonetizeFreeChatCount({
|
||||
isOn,
|
||||
disabled,
|
||||
freeCount,
|
||||
onFreeCountChange,
|
||||
}: {
|
||||
isOn: boolean;
|
||||
disabled: boolean;
|
||||
freeCount: number;
|
||||
onFreeCountChange: (value: number) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="flex items-center font-semibold leading-[20px]">
|
||||
<span className="coz-fg-primary">
|
||||
{I18n.t('free_chat_allowance')}
|
||||
</span>
|
||||
<Tooltip theme="dark" content={I18n.t('free_chat_allowance_tips')}>
|
||||
<span className="ml-[4px] h-[12px] w-[12px] text-[12px] leading-[12px] coz-fg-secondary">
|
||||
<IconCozInfoCircle />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="coz-fg-secondary text-base leading-[16px]">
|
||||
{freeCount > 5
|
||||
? I18n.t('coze_premium_credits_cycle_tip2')
|
||||
: I18n.t('coze_premium_credits_cycle_tip3')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<InputNumber
|
||||
keepFocus
|
||||
className="w-[140px]"
|
||||
disabled={!isOn || disabled}
|
||||
precision={0}
|
||||
min={0}
|
||||
max={100}
|
||||
value={freeCount}
|
||||
onNumberChange={onFreeCountChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 cls from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozInfoCircle } from '@coze-arch/coze-design/icons';
|
||||
import { Avatar, Tooltip } from '@coze-arch/coze-design';
|
||||
import { type PublishConnectorInfo } from '@coze-arch/bot-api/developer_api';
|
||||
import { type BotMonetizationConfigData } from '@coze-arch/bot-api/benefit';
|
||||
|
||||
export function MonetizePublishInfo({
|
||||
className,
|
||||
monetizeConfig,
|
||||
supportPlatforms,
|
||||
}: {
|
||||
className?: string;
|
||||
monetizeConfig: BotMonetizationConfigData;
|
||||
supportPlatforms: Array<Pick<PublishConnectorInfo, 'id' | 'name' | 'icon'>>;
|
||||
}) {
|
||||
const supportPlatformsText = supportPlatforms.map(p => p.name).join(', ');
|
||||
|
||||
return (
|
||||
<div className={cls('flex justify-end items-center gap-[12px]', className)}>
|
||||
<div className="flex items-center gap-[4px]">
|
||||
<span className="font-medium coz-fg-plus">
|
||||
{`${I18n.t('monetization')}: ${
|
||||
monetizeConfig.is_enable
|
||||
? I18n.t('monetization_publish_on')
|
||||
: I18n.t('monetization_publish_off')
|
||||
}`}
|
||||
</span>
|
||||
<Tooltip
|
||||
content={
|
||||
<div className="flex flex-col">
|
||||
<div>
|
||||
{monetizeConfig.is_enable
|
||||
? I18n.t('monetization_on_des')
|
||||
: I18n.t('monetization_off_des')}
|
||||
</div>
|
||||
{monetizeConfig.is_enable ? (
|
||||
<div className="mt-[8px] pt-[8px] border-0 border-t border-solid coz-stroke-primary">
|
||||
{`${I18n.t('free_chat_allowance')} : ${
|
||||
monetizeConfig.free_chat_allowance_count
|
||||
}`}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<IconCozInfoCircle className="w-[16px] h-[16px] coz-fg-secondary" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-[4px]">
|
||||
<span className="font-normal coz-fg-tertiary">
|
||||
{I18n.t('monetization_support')}:
|
||||
</span>
|
||||
<span className="flex items-center gap-[4px]">
|
||||
{supportPlatforms.map(p => (
|
||||
<Avatar
|
||||
key={p.id}
|
||||
className="h-[16px] w-[16px] rounded-[4px]"
|
||||
size="extra-extra-small"
|
||||
shape="square"
|
||||
src={p.icon}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
<Tooltip
|
||||
content={`${I18n.t(
|
||||
'monetization_support_tips',
|
||||
)}: ${supportPlatformsText}`}
|
||||
>
|
||||
<IconCozInfoCircle className="w-[16px] h-[16px] coz-fg-secondary" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 { I18n } from '@coze-arch/i18n';
|
||||
import { Switch } from '@coze-arch/coze-design';
|
||||
|
||||
export function MonetizeSwitch({
|
||||
disabled,
|
||||
isOn,
|
||||
onChange,
|
||||
}: {
|
||||
disabled: boolean;
|
||||
isOn: boolean;
|
||||
onChange: (isOn: boolean) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex justify-between">
|
||||
<h3 className="m-0 text-[20px] font-medium coz-fg-plus">
|
||||
{I18n.t('premium_monetization_config')}
|
||||
</h3>
|
||||
<Switch
|
||||
disabled={disabled}
|
||||
className="ml-[5px]"
|
||||
size="small"
|
||||
checked={isOn}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { TopBar } from './top-bar';
|
||||
export { SpaceAppList } from './space-app-list';
|
||||
@@ -0,0 +1,49 @@
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
padding: 6px 14px;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
@apply coz-fg-secondary;
|
||||
|
||||
&:hover {
|
||||
@apply coz-mg-primary coz-fg-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
@apply coz-mg-primary coz-fg-primary;
|
||||
}
|
||||
|
||||
.item-link {
|
||||
margin-bottom: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tag-container {
|
||||
@apply flex items-center gap-4px;
|
||||
|
||||
.label {
|
||||
@apply px-4px text-foreground-2 font-semibold text-lg;
|
||||
}
|
||||
|
||||
.tag {
|
||||
@apply rounded-mini;
|
||||
|
||||
padding: 1px 6px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
.label {
|
||||
@apply coz-fg-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 { NavLink } from 'react-router-dom';
|
||||
import React, { type ReactNode } from 'react';
|
||||
|
||||
import { isString } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { SpaceAppEnum } from '@coze-arch/web-context';
|
||||
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
|
||||
import { Space, Badge } from '@coze-arch/coze-design';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { getFlags } from '@coze-arch/bot-flags';
|
||||
import { KnowledgeE2e, BotE2e } from '@coze-data/e2e';
|
||||
import { useSpaceApp } from '@coze-foundation/space-store';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface MenuItem {
|
||||
/**
|
||||
* 如果是string,需传入starling key,并且会由div包一层
|
||||
* 如果是function,则自定义label的实现,active表示是否是选中态
|
||||
*/
|
||||
label: string | ((active: boolean) => React.ReactNode);
|
||||
/** label 外的 badge,未来再扩展配置项 */
|
||||
badge?: string;
|
||||
app: SpaceAppEnum;
|
||||
/**
|
||||
* Q:为什么不叫 visible?FG 要取反,filter() 也要取反,很麻烦
|
||||
* A:为了兼容旧配置,缺省时认定为 visible。避免合码时无冲突 导致忽略掉新增配置的问题。
|
||||
*/
|
||||
invisible?: boolean;
|
||||
/** 目前(24.05.21)没发现用处,怀疑是以前的功能迭代掉了,@huangjian 说先留着 */
|
||||
icon?: ReactNode;
|
||||
/** 目前(24.05.21)没发现用处,怀疑是以前的功能迭代掉了,@huangjian 说先留着 */
|
||||
selectedIcon?: ReactNode;
|
||||
/** 自动化打标 */
|
||||
e2e?: string;
|
||||
}
|
||||
|
||||
const GET_MENU_SPACE_APP = (): Array<MenuItem> => [
|
||||
{
|
||||
label: 'menu_bots',
|
||||
app: SpaceAppEnum.BOT,
|
||||
e2e: BotE2e.BotTab,
|
||||
},
|
||||
{
|
||||
label: 'menu_plugins',
|
||||
app: SpaceAppEnum.PLUGIN,
|
||||
},
|
||||
{
|
||||
label: 'menu_workflows',
|
||||
app: SpaceAppEnum.WORKFLOW,
|
||||
},
|
||||
{
|
||||
label: 'imageflow_title',
|
||||
app: SpaceAppEnum.IMAGEFLOW,
|
||||
invisible: false,
|
||||
},
|
||||
{
|
||||
label: 'menu_datasets',
|
||||
app: SpaceAppEnum.KNOWLEDGE,
|
||||
e2e: KnowledgeE2e.KnowledgeTab,
|
||||
},
|
||||
{
|
||||
label: 'menu_widgets',
|
||||
app: SpaceAppEnum.WIDGET,
|
||||
invisible: !getFlags()['bot.builder.bot.builder.widget'],
|
||||
},
|
||||
{
|
||||
label: 'scene_resource_name',
|
||||
badge: 'scene_beta_sign',
|
||||
app: SpaceAppEnum.SOCIAL_SCENE,
|
||||
invisible: !getFlags()['bot.studio.social'],
|
||||
},
|
||||
];
|
||||
export const SpaceAppList = () => {
|
||||
const spaceApp = useSpaceApp();
|
||||
|
||||
const { id: spaceId } = useSpaceStore(store => store.space);
|
||||
|
||||
return (
|
||||
<Space spacing={4}>
|
||||
{GET_MENU_SPACE_APP()
|
||||
.filter(item => !item.invisible)
|
||||
.map(item => {
|
||||
const active = item.app === spaceApp;
|
||||
const tabContent = (
|
||||
<NavLink
|
||||
key={item.app}
|
||||
data-testid={item.e2e}
|
||||
to={`/space/${spaceId}/${item.app}`}
|
||||
className={s['item-link']}
|
||||
onClick={() => {
|
||||
sendTeaEvent(EVENT_NAMES.workspace_tab_expose, {
|
||||
tab_name: item.app,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{isString(item.label) ? (
|
||||
<div
|
||||
className={classNames({
|
||||
[s.item]: true,
|
||||
[s.active]: active,
|
||||
})}
|
||||
>
|
||||
{I18n.t(item.label as I18nKeysNoOptionsType)}
|
||||
</div>
|
||||
) : (
|
||||
item.label(active)
|
||||
)}
|
||||
</NavLink>
|
||||
);
|
||||
|
||||
return item.badge ? (
|
||||
<Badge
|
||||
type="alt"
|
||||
key={item.app}
|
||||
count={I18n.t(item.badge as I18nKeysNoOptionsType)}
|
||||
countStyle={{
|
||||
backgroundColor: 'var(--coz-mg-color-plus-emerald)',
|
||||
}}
|
||||
>
|
||||
{tabContent}
|
||||
</Badge>
|
||||
) : (
|
||||
tabContent
|
||||
);
|
||||
})}
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
.topBar {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
|
||||
.des {
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.name {
|
||||
max-width: calc(100% - 28px);
|
||||
word-break: break-word;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 28px;
|
||||
height: 28px;
|
||||
@apply coz-fg-plus;
|
||||
}
|
||||
|
||||
.split {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
margin: 0 4px;
|
||||
border-bottom: none;
|
||||
background-color: var(--coz-stroke-primary);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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 React, { type JSX } from 'react';
|
||||
|
||||
import classnames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozSetting } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
Typography,
|
||||
Space,
|
||||
IconButton,
|
||||
Divider,
|
||||
Avatar,
|
||||
} from '@coze-arch/coze-design';
|
||||
import { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
import { useSpaceStore } from '@coze-arch/bot-studio-store';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { SpaceAppList } from '../space-app-list';
|
||||
|
||||
import s from './index.module.less';
|
||||
interface TopBarProps {
|
||||
showActions?: boolean;
|
||||
showFilter?: boolean;
|
||||
isPersonal?: boolean;
|
||||
actions: JSX.Element;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
titleExtend?: JSX.Element;
|
||||
}
|
||||
|
||||
export const TopBar = (props: TopBarProps) => {
|
||||
const { Text } = Typography;
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
space: { name: spaceName, id: spaceId, icon_url: spaceIconUrl },
|
||||
} = useSpaceStore();
|
||||
const {
|
||||
showActions,
|
||||
showFilter,
|
||||
isPersonal,
|
||||
actions,
|
||||
children,
|
||||
className,
|
||||
titleExtend,
|
||||
} = props;
|
||||
const settingLabel = I18n.t('basic_setting');
|
||||
|
||||
return (
|
||||
<div className={classnames(s.topBar, className)}>
|
||||
<Space className="w-full flex justify-between mb-24px">
|
||||
<Space>
|
||||
<div className={s.des}>
|
||||
<Avatar
|
||||
src={spaceIconUrl}
|
||||
size="small"
|
||||
style={{ marginRight: 8 }}
|
||||
/>
|
||||
<Text
|
||||
ellipsis={{
|
||||
showTooltip: {
|
||||
opts: { content: spaceName },
|
||||
},
|
||||
}}
|
||||
className={classnames(s.name, '!max-w-[320px]')}
|
||||
>
|
||||
{spaceName}
|
||||
</Text>
|
||||
{titleExtend}
|
||||
</div>
|
||||
</Space>
|
||||
<Space spacing={8} className="flex items-center align-right">
|
||||
{showActions ? actions : null}
|
||||
{!isPersonal && (
|
||||
<>
|
||||
<Divider layout="horizontal" className={s.split} />
|
||||
<IconButton
|
||||
color="primary"
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={() => {
|
||||
sendTeaEvent(EVENT_NAMES.workspace_tab_expose, {
|
||||
tab_name: 'team_manage',
|
||||
});
|
||||
|
||||
navigate(`/space/${spaceId}/team`);
|
||||
}}
|
||||
icon={<IconCozSetting />}
|
||||
aria-label={settingLabel}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
</Space>
|
||||
<div className={s.tabs}>
|
||||
<Space className="w-full flex justify-between">
|
||||
<Space spacing={8} className="flex items-center align-left shrink-0">
|
||||
<SpaceAppList />
|
||||
</Space>
|
||||
<Space
|
||||
className="!flex items-center !overflow-hidden shrink-1"
|
||||
spacing={8}
|
||||
>
|
||||
{showFilter ? children : null}
|
||||
</Space>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
.plugin-limit-table {
|
||||
:global {
|
||||
.semi-table-thead>.semi-table-row>.semi-table-row-head {
|
||||
height: 16px;
|
||||
padding: 4px 8px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.semi-table-row{
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.semi-table-tbody .semi-table-row .semi-table-row-cell {
|
||||
padding: 8px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* 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 React, { type FC } from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Avatar, Typography, UITable, useUIModal } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
type PluginPricingRule,
|
||||
PluginPricingStrategy,
|
||||
} from '@coze-arch/bot-api/plugin_develop';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export const PluginLimitName: FC<{
|
||||
name: string;
|
||||
url: string;
|
||||
}> = ({ name, url }) => (
|
||||
<span className="flex items-center">
|
||||
<Avatar
|
||||
size="small"
|
||||
className="h-6 flex-none flex-shrink-0 mr-2 w-6"
|
||||
shape="square"
|
||||
src={url}
|
||||
></Avatar>
|
||||
<Typography.Text ellipsis={{ showTooltip: true }}>{name}</Typography.Text>
|
||||
</span>
|
||||
);
|
||||
|
||||
export interface UsePluginLimitModalProps {
|
||||
content: React.ReactNode;
|
||||
dataSource: Array<{
|
||||
info: {
|
||||
name: string;
|
||||
url: string;
|
||||
};
|
||||
price: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export const transPricingRules = (
|
||||
pluginPricingRules?: Array<PluginPricingRule>,
|
||||
) =>
|
||||
Array.isArray(pluginPricingRules)
|
||||
? pluginPricingRules
|
||||
.filter(item => item.PricingStrategy !== PluginPricingStrategy.Free)
|
||||
.map(item => ({
|
||||
info: {
|
||||
name: item?.PluginInfo?.name,
|
||||
url: item?.PluginInfo?.plugin_icon,
|
||||
},
|
||||
price: parseInt(item?.PriceResult?.TokensForOnce ?? '0'),
|
||||
}))
|
||||
: [];
|
||||
|
||||
export const usePluginLimitModal = ({
|
||||
content,
|
||||
dataSource,
|
||||
}: UsePluginLimitModalProps) => {
|
||||
const { modal, open, close } = useUIModal({
|
||||
okText: I18n.t('plugin_usage_limits_modal_got_it_button'),
|
||||
onOk: () => {
|
||||
close();
|
||||
},
|
||||
title: I18n.t('plugin_usage_limits_modal_title'),
|
||||
hasCancel: false,
|
||||
onCancel: () => {
|
||||
close();
|
||||
},
|
||||
});
|
||||
return {
|
||||
node: modal(
|
||||
<>
|
||||
{content ? content : null}
|
||||
<UITable
|
||||
tableProps={{
|
||||
className: styles['plugin-limit-table'],
|
||||
columns: [
|
||||
{
|
||||
width: 192,
|
||||
title: I18n.t('plugin_usage_limits_modal_table_header_plugin'),
|
||||
dataIndex: 'info',
|
||||
render: info => (
|
||||
<PluginLimitName name={info.name} url={info.url} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: I18n.t('plugin_usage_limits_modal_table_header_price'),
|
||||
dataIndex: 'price',
|
||||
render: text => <span>{text} Coze tokens</span>,
|
||||
},
|
||||
],
|
||||
dataSource,
|
||||
size: 'small',
|
||||
}}
|
||||
/>
|
||||
</>,
|
||||
),
|
||||
open: () => {
|
||||
open();
|
||||
},
|
||||
close,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,859 @@
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
/* stylelint-disable no-descending-specificity */
|
||||
/* stylelint-disable declaration-no-important */
|
||||
/* stylelint-disable max-nesting-depth */
|
||||
@import '@coze-common/assets/style/common.less';
|
||||
@import '@coze-common/assets/style/mixins.less';
|
||||
|
||||
.text {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.plugin-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
box-sizing: border-box;
|
||||
width: calc(100% - 68px);
|
||||
height: 98px;
|
||||
margin: 14px 0 0 68px;
|
||||
padding-right: 12px;
|
||||
padding-bottom: 14px;
|
||||
|
||||
&:not(:last-child) {
|
||||
position: relative;
|
||||
border-bottom: 1px solid rgba(28, 31, 35, 8%);
|
||||
}
|
||||
|
||||
.plugin-api-main {
|
||||
flex: 1;
|
||||
|
||||
.plugin-api-name {
|
||||
width: 100%;
|
||||
|
||||
:global {
|
||||
.semi-typography {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
color: #1c1d23 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-api-desc {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
|
||||
:global {
|
||||
.semi-typography {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color:
|
||||
rgba(28, 31, 35, 60%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.api-params {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 12px;
|
||||
|
||||
.params-tags {
|
||||
max-width: 500px;
|
||||
|
||||
.tag-item {
|
||||
width: fit-content;
|
||||
min-width: fit-content;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
|
||||
/* 133.333% */
|
||||
color: rgba(28, 29, 35, 60%);
|
||||
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&> :not(:first-child) {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.params-desc {
|
||||
cursor: pointer;
|
||||
|
||||
flex-shrink: 0;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #4d53e8;
|
||||
letter-spacing: 0.12px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-api-method {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tools-content {
|
||||
padding: 0 20px 24px;
|
||||
|
||||
.tools-table-thead {
|
||||
padding-bottom: 6px;
|
||||
|
||||
th {
|
||||
padding: 6px 0 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.api-table {
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
|
||||
/* 133.333% */
|
||||
color: rgba(28, 31, 35, 60%);
|
||||
word-wrap: break-word;
|
||||
|
||||
thead {
|
||||
th {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
|
||||
/* 133.333% */
|
||||
color: #888d92;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.api-row {
|
||||
&.border-top {
|
||||
position: relative;
|
||||
|
||||
td {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 1px solid #f9f9f9;
|
||||
}
|
||||
|
||||
td {
|
||||
overflow: hidden;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
&-name {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-tag-grey-light {
|
||||
height: 20px;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.semi-tag-content {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.api-plugin {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
padding-right: 10px;
|
||||
|
||||
&-image {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
|
||||
>img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-image-status {
|
||||
background-color: rgba(#fff, 0) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-name {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
color: rgba(28, 29, 35, 80%);
|
||||
}
|
||||
}
|
||||
|
||||
.api-method {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
color: rgba(28, 31, 35, 35%);
|
||||
}
|
||||
|
||||
.icon-config {
|
||||
cursor: pointer;
|
||||
color: rgba(107, 109, 117, 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.api-method-read {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.api-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 0;
|
||||
padding-right: 10px;
|
||||
|
||||
&-text {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
color: rgba(29, 28, 35, 80%);
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-tag-grey-light {
|
||||
background: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
.api-divider {
|
||||
height: 12px;
|
||||
border-color: #b3c4ff;
|
||||
}
|
||||
|
||||
.copy {
|
||||
cursor: copy;
|
||||
}
|
||||
|
||||
.icon-tips:hover {
|
||||
background-color: var(--semi-color-fill-0);
|
||||
}
|
||||
|
||||
.icon-tips {
|
||||
cursor: pointer;
|
||||
.common-svg-icon(14px, rgba(107, 109, 117, 1));
|
||||
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&-publish {
|
||||
flex-shrink: 0;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover-content {
|
||||
box-sizing: border-box;
|
||||
max-width: 264px;
|
||||
}
|
||||
|
||||
.popover-api-name {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 20px;
|
||||
color: #1c1f23;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.popover-api-desc {
|
||||
padding: 4px 0;
|
||||
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: rgba(28, 31, 35, 60%);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.plugin-panel-header {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
height: 80px;
|
||||
|
||||
font-weight: 400;
|
||||
|
||||
.creator-icon {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
|
||||
img {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.creator-time {
|
||||
/* Paragraph/small/EN-Regular */
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
|
||||
/* 133.333% */
|
||||
color: rgba(28, 29, 35, 35%);
|
||||
text-align: right;
|
||||
letter-spacing: 0.12px;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
margin-right: 16px;
|
||||
|
||||
img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.header-main {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
margin-right: 16px;
|
||||
|
||||
// margin-top: -12px;
|
||||
.header-name {
|
||||
width: 100%;
|
||||
|
||||
:global {
|
||||
.semi-typography {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
color: #1c1d23 !important;
|
||||
word-wrap: break-word !important;
|
||||
}
|
||||
|
||||
.semi-highlight-tag {
|
||||
color: #fda633;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-desc {
|
||||
width: 100%;
|
||||
|
||||
:global {
|
||||
.semi-typography {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color:
|
||||
rgba(28, 29, 35, 80%) !important;
|
||||
letter-spacing: 0.12px;
|
||||
word-wrap: break-word !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-info {
|
||||
/* Paragraph/small/EN-Regular */
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 16px;
|
||||
|
||||
/* 133.333% */
|
||||
color: rgba(28, 29, 35, 35%);
|
||||
text-align: right;
|
||||
|
||||
:global {
|
||||
.semi-divider-vertical {
|
||||
height: 10px;
|
||||
color: rgba(28, 29, 35, 12%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-tags {
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.header-tag {
|
||||
letter-spacing: 0.12px !important;
|
||||
border-radius: 6px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
padding-right: 24px;
|
||||
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
color: #1c1f23;
|
||||
|
||||
&-title {
|
||||
width: 218px;
|
||||
padding: 24px;
|
||||
line-height: 16px;
|
||||
background: #ebedf0;
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-modal-filter {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.composition-modal-layout {
|
||||
background: #ebedf0;
|
||||
}
|
||||
|
||||
.plugin-modal {
|
||||
height: 100%;
|
||||
|
||||
.iconSearch {
|
||||
.common-svg-icon(16px, rgba(28, 29, 35, 0.35));
|
||||
|
||||
margin-right: 8px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.tool-tag-list {
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
flex-shrink: 0;
|
||||
|
||||
padding-top: 16px;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
&-label {
|
||||
height: 40px;
|
||||
margin-bottom: 8px;
|
||||
padding: 0 12px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 40px;
|
||||
color: rgba(28, 29, 35, 35%);
|
||||
}
|
||||
|
||||
&-cell {
|
||||
cursor: pointer;
|
||||
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
height: 44px;
|
||||
margin-bottom: 4px;
|
||||
padding: 0 10px 0 12px;
|
||||
|
||||
font-size: 14px;
|
||||
line-height: 44px;
|
||||
color: #1d1c23;
|
||||
|
||||
border-radius: 3px;
|
||||
|
||||
&-icon {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
margin-right: 8px;
|
||||
.common-svg-icon(24px, #1d1c23);
|
||||
|
||||
>img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
&-divider {
|
||||
width: calc(100% - 24px);
|
||||
margin: 12px;
|
||||
background: rgba(28, 29, 35, 12%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #1c1f23;
|
||||
background:
|
||||
rgba(46, 50, 56, 5%);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--light-usage-text-color-text-0, #1c1d23);
|
||||
|
||||
background:
|
||||
rgba(46, 47, 56, 5%);
|
||||
border-radius: 8px;
|
||||
|
||||
.tool-tag-list-cell-icon {
|
||||
.common-svg-icon(24px, #4d53e8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.addbtn {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.plugin-filter {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
|
||||
width: 218px;
|
||||
|
||||
background: #ebedf0;
|
||||
}
|
||||
|
||||
.tool-content-area {
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.plugin-modal-filter {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.plugin-content {
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-bottom: 12px;
|
||||
|
||||
.loading-more {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.plugin-content-filter {
|
||||
display: flex;
|
||||
padding: 0 36px;
|
||||
padding-left: 22px;
|
||||
|
||||
.plugin-content-sort {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.bot-tag {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-tabs-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.semi-tabs-tab-button.semi-tabs-tab-active {
|
||||
color: rgba(28, 29, 35, 80%);
|
||||
background-color: transparent;
|
||||
|
||||
.semi-icon {
|
||||
.common-svg-icon(20px, rgba(28, 29, 35, 0.8));
|
||||
}
|
||||
}
|
||||
|
||||
.semi-tabs-tab-single.semi-tabs-tab-active .semi-icon:not(.semi-icon-checkbox_tick,
|
||||
.semi-icon-radio,
|
||||
.semi-icon-checkbox_indeterminate) {
|
||||
top: 0;
|
||||
color: rgba(28, 29, 35, 80%);
|
||||
}
|
||||
|
||||
.semi-tabs-tab-single.semi-tabs-tab .semi-icon:not(.semi-icon-checkbox_tick,
|
||||
.semi-icon-radio,
|
||||
.semi-icon-checkbox_indeterminate) {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.semi-tabs-tab:last-child::before {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: -4px;
|
||||
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
|
||||
background-color:
|
||||
rgba(28, 29, 35, 12%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-collapse {
|
||||
:global {
|
||||
.semi-collapse-item {
|
||||
position: relative;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.semi-collapse-header:hover::before {
|
||||
content: '';
|
||||
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
|
||||
background-color: #f7f7fa;
|
||||
}
|
||||
|
||||
.semi-collapse-header {
|
||||
margin: 0;
|
||||
border-bottom: 1px solid #dfdfdf;
|
||||
border-radius: 0;
|
||||
|
||||
&:hover {
|
||||
background:
|
||||
rgba(46, 47, 56, 5%);
|
||||
border-bottom: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background:
|
||||
rgba(46, 47, 56, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
.semi-collapse-header-icon {
|
||||
width: auto;
|
||||
height: 24px;
|
||||
|
||||
&:hover {
|
||||
background:
|
||||
rgba(46, 47, 56, 9%);
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-icon {
|
||||
.common-svg-icon(16px, rgba(28, 29, 35, 0.35));
|
||||
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.activePanel {
|
||||
margin-bottom: 8px;
|
||||
background:
|
||||
rgba(46, 47, 56, 5%);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
|
||||
:global {
|
||||
.semi-collapse-header {
|
||||
border-bottom: 1px solid #dfdfdf;
|
||||
}
|
||||
|
||||
.semi-collapse-header:hover {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global {
|
||||
.semi-modal-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.semi-spin-children {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-content,
|
||||
.plugin-collapse {
|
||||
:global {
|
||||
.semi-collapse-header {
|
||||
height: 120px !important;
|
||||
margin: 0 !important;
|
||||
padding: 14px 16px;
|
||||
|
||||
&[aria-expanded='true'] {
|
||||
border-radius: 8px 8px 0 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.semi-collapse {
|
||||
padding: 16px 0 12px;
|
||||
}
|
||||
|
||||
.semi-collapse-content {
|
||||
// background-color: #fff;
|
||||
padding: 0;
|
||||
border-radius: 0 0 8px 8px;
|
||||
// border-color: var(
|
||||
// --light-usage-border-color-border,
|
||||
// rgba(28, 31, 35, 0.08)
|
||||
// );
|
||||
// border-width: 0 1px 1px 1px;
|
||||
// border-style: solid;
|
||||
}
|
||||
|
||||
.semi-collapse-item {
|
||||
// border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.parameter-item {
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
|
||||
.parameter-text {
|
||||
max-width: 100%;
|
||||
margin-bottom: 4px;
|
||||
color: #1c1f23;
|
||||
|
||||
.parameter-name {
|
||||
font-weight: 700;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.parameter-type {
|
||||
color: rgba(28, 31, 35, 80%);
|
||||
}
|
||||
|
||||
.parameter-required {
|
||||
color: #fc8800;
|
||||
}
|
||||
}
|
||||
|
||||
.parameter-desc {
|
||||
color: rgba(28, 31, 35, 60%);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.apis-add-icon {
|
||||
.common-svg-icon;
|
||||
}
|
||||
|
||||
.default-text {
|
||||
.text;
|
||||
|
||||
color: rgba(28, 29, 35, 60%);
|
||||
}
|
||||
|
||||
.operator-btn {
|
||||
width: 98px;
|
||||
|
||||
&.added {
|
||||
color: #b4baf6;
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f5;
|
||||
}
|
||||
|
||||
&.addedMouseIn {
|
||||
color: #ff441e;
|
||||
background: #fff;
|
||||
border: 1px solid rgba(29, 28, 35, 12%);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-select {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.hide-button-model-wrap {
|
||||
.ml20 {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.h56 {
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
top: 20px;
|
||||
|
||||
width: 100%;
|
||||
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.plugin-func-collapse {
|
||||
.plugin-api-desc {
|
||||
cursor: pointer;
|
||||
width: 200px !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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 PropsWithChildren } from 'react';
|
||||
|
||||
import { sortBy } from 'lodash-es';
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type PopoverProps } from '@coze-arch/bot-semi/Popover';
|
||||
import { Popover, Space } from '@coze-arch/bot-semi';
|
||||
import {
|
||||
PluginParamTypeFormat,
|
||||
type PluginApi,
|
||||
type PluginParameter,
|
||||
} from '@coze-arch/bot-api/developer_api';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
interface ParametersPopoverProps extends PopoverProps {
|
||||
pluginApi: PluginApi;
|
||||
callback?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
onVisibleChange?: (visible: boolean) => void;
|
||||
}
|
||||
|
||||
enum AssistType {
|
||||
File = 1,
|
||||
Image = 2,
|
||||
Doc = 3,
|
||||
Code = 4,
|
||||
Ppt = 5,
|
||||
Txt = 6,
|
||||
Excel = 7,
|
||||
Audio = 8,
|
||||
Zip = 9,
|
||||
Video = 10,
|
||||
Svg = 11,
|
||||
}
|
||||
|
||||
const assistTypeToDisplayMap = {
|
||||
[AssistType.File]: 'File',
|
||||
[AssistType.Image]: 'Image',
|
||||
[AssistType.Doc]: 'Doc',
|
||||
[AssistType.Code]: 'Code',
|
||||
[AssistType.Ppt]: 'PPT',
|
||||
[AssistType.Txt]: 'Txt',
|
||||
[AssistType.Excel]: 'Excel',
|
||||
[AssistType.Audio]: 'Audio',
|
||||
[AssistType.Zip]: 'Zip',
|
||||
[AssistType.Video]: 'Video',
|
||||
[AssistType.Svg]: 'Svg',
|
||||
};
|
||||
|
||||
const getDisplayType = (parameter: Readonly<PluginParameter>) => {
|
||||
const { type, format } = parameter;
|
||||
|
||||
const { assist_type } = parameter as { assist_type: AssistType };
|
||||
|
||||
let displayType = type;
|
||||
if (type === 'string' && format === PluginParamTypeFormat.ImageUrl) {
|
||||
displayType = 'image';
|
||||
} else if (type === 'string' && assist_type) {
|
||||
displayType = assistTypeToDisplayMap[assist_type];
|
||||
}
|
||||
return displayType;
|
||||
};
|
||||
|
||||
const ParameterItem: React.FC<{ parameter: Readonly<PluginParameter> }> = ({
|
||||
parameter,
|
||||
}) => {
|
||||
const { name, desc, required } = parameter;
|
||||
|
||||
return (
|
||||
<div className={s['parameter-item']}>
|
||||
<Space className={s['parameter-text']} wrap>
|
||||
<span className={s['parameter-name']}>{name}</span>
|
||||
<span className={s['parameter-type']}>{getDisplayType(parameter)}</span>
|
||||
{required ? (
|
||||
<span className={s['parameter-required']}>
|
||||
{I18n.t('tool_para_required')}
|
||||
</span>
|
||||
) : null}
|
||||
</Space>
|
||||
<div className={s['parameter-desc']}>{desc}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ParametersPopover: React.FC<
|
||||
PropsWithChildren<ParametersPopoverProps>
|
||||
> = ({ children, pluginApi, callback, onVisibleChange, ...props }) => (
|
||||
<Popover
|
||||
trigger={props?.trigger || 'hover'}
|
||||
position="right"
|
||||
showArrow
|
||||
onVisibleChange={onVisibleChange}
|
||||
content={
|
||||
<div
|
||||
className={classNames(
|
||||
'max-h-[400px] overflow-x-hidden overflow-y-auto',
|
||||
s['popover-content'],
|
||||
)}
|
||||
onClick={e => {
|
||||
callback?.(e);
|
||||
}}
|
||||
>
|
||||
{pluginApi.name ? (
|
||||
<div className={s['popover-api-name']}>{pluginApi.name}</div>
|
||||
) : null}
|
||||
{pluginApi.desc ? (
|
||||
<div className={s['popover-api-desc']}>{pluginApi.desc}</div>
|
||||
) : null}
|
||||
{sortBy(pluginApi.parameters || [], item => item.name?.length)?.map(
|
||||
p => {
|
||||
if (!p) {
|
||||
return null;
|
||||
}
|
||||
return <ParameterItem parameter={p} key={p.name} />;
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
{...props}
|
||||
>
|
||||
<div>{children}</div>
|
||||
</Popover>
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
.tip-content {
|
||||
width: 180px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 18px;
|
||||
color: var(--light-color-grey-grey-8, #2e3238);
|
||||
|
||||
li {
|
||||
list-style-type: disc;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 React, {
|
||||
Suspense,
|
||||
lazy,
|
||||
type PropsWithChildren,
|
||||
type ReactNode,
|
||||
type CSSProperties,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import s from './index.module.less';
|
||||
// react-markdown 20ms 左右的 longtask
|
||||
const LazyReactMarkdown = lazy(() => import('react-markdown'));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const ReactMarkdown = (props: any) => (
|
||||
<Suspense fallback={null}>
|
||||
<LazyReactMarkdown {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
export const PopoverContent: React.FC<
|
||||
PropsWithChildren & {
|
||||
text?: string;
|
||||
node?: ReactNode;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
> = ({ children, className, style }) => (
|
||||
<div className={classNames(s['tip-content'], className)} style={style}>
|
||||
{typeof children === 'string' ? (
|
||||
<ReactMarkdown skipHtml={true} className={s.markdown}>
|
||||
{children}
|
||||
</ReactMarkdown>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* 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 { useRef, useState } from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { extractTemplateActionCommonParams } from '@coze-arch/bot-tea/utils';
|
||||
import {
|
||||
EVENT_NAMES,
|
||||
type ParamsTypeDefine,
|
||||
sendTeaEvent,
|
||||
} from '@coze-arch/bot-tea';
|
||||
import {
|
||||
ProductEntityType,
|
||||
type ProductInfo,
|
||||
} from '@coze-arch/bot-api/product_api';
|
||||
import { ProductApi } from '@coze-arch/bot-api';
|
||||
import { botInputLengthService } from '@coze-agent-ide/bot-input-length-limit';
|
||||
import {
|
||||
type BaseFormProps,
|
||||
Form,
|
||||
FormInput,
|
||||
Modal,
|
||||
type ModalProps,
|
||||
type FormApi,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { SpaceFormSelect } from '../space-form-select';
|
||||
import { appendCopySuffix } from './utils';
|
||||
|
||||
export interface ProjectTemplateCopyValue {
|
||||
productId: string;
|
||||
name: string;
|
||||
spaceId?: string;
|
||||
}
|
||||
|
||||
const filedKeyMap: Record<
|
||||
keyof ProjectTemplateCopyValue,
|
||||
keyof ProjectTemplateCopyValue
|
||||
> = {
|
||||
name: 'name',
|
||||
spaceId: 'spaceId',
|
||||
productId: 'productId',
|
||||
} as const;
|
||||
|
||||
interface ProjectTemplateCopyModalProps
|
||||
extends Omit<ModalProps, 'size' | 'okText' | 'cancelText'> {
|
||||
isSelectSpace: boolean;
|
||||
formProps: BaseFormProps<ProjectTemplateCopyValue>;
|
||||
}
|
||||
|
||||
export const ProjectTemplateCopyModal: React.FC<
|
||||
ProjectTemplateCopyModalProps
|
||||
> = ({ isSelectSpace, formProps, ...modalProps }) => (
|
||||
<Modal
|
||||
size="default"
|
||||
okText={I18n.t('Confirm')}
|
||||
cancelText={I18n.t('Cancel')}
|
||||
{...modalProps}
|
||||
>
|
||||
<Form<ProjectTemplateCopyValue> {...formProps}>
|
||||
<FormInput
|
||||
label={I18n.t('creat_project_project_name')}
|
||||
rules={[{ required: true }]}
|
||||
field={filedKeyMap.name}
|
||||
maxLength={botInputLengthService.getInputLengthLimit('projectName')}
|
||||
getValueLength={botInputLengthService.getValueLength}
|
||||
noErrorMessage
|
||||
/>
|
||||
{isSelectSpace ? <SpaceFormSelect field={filedKeyMap.spaceId} /> : null}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
export type ProjectTemplateCopySuccessCallback = (param: {
|
||||
originProductId: string;
|
||||
newEntityId: string;
|
||||
spaceId: string;
|
||||
}) => void;
|
||||
|
||||
export const useProjectTemplateCopyModal = (props: {
|
||||
modalTitle: string;
|
||||
/** 是否需要选择 space */
|
||||
isSelectSpace: boolean;
|
||||
onSuccess?: ProjectTemplateCopySuccessCallback;
|
||||
/** 埋点参数 - 当前页面/来源 */
|
||||
source: NonNullable<
|
||||
ParamsTypeDefine[EVENT_NAMES.template_action_front]['source']
|
||||
>;
|
||||
}) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [initValues, setInitValues] = useState<ProjectTemplateCopyValue>();
|
||||
const [sourceProduct, setSourceProduct] = useState<ProductInfo>();
|
||||
const [isFormValid, setIsFormValid] = useState(true);
|
||||
const formApi = useRef<FormApi<ProjectTemplateCopyValue>>();
|
||||
|
||||
const onModalClose = () => {
|
||||
setVisible(false);
|
||||
setInitValues(undefined);
|
||||
formApi.current = undefined;
|
||||
setIsFormValid(true);
|
||||
};
|
||||
|
||||
const { run, loading } = useRequest(
|
||||
async (copyRequestParam: ProjectTemplateCopyValue | undefined) => {
|
||||
if (!copyRequestParam) {
|
||||
throw new Error('duplicate project template values not provided');
|
||||
}
|
||||
const { productId, spaceId, name } = copyRequestParam;
|
||||
return ProductApi.PublicDuplicateProduct({
|
||||
product_id: productId,
|
||||
space_id: spaceId,
|
||||
name,
|
||||
entity_type: ProductEntityType.ProjectTemplate,
|
||||
});
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: (data, [inputParam]) => {
|
||||
onModalClose();
|
||||
sendTeaEvent(EVENT_NAMES.template_action_front, {
|
||||
action: 'duplicate',
|
||||
after_id: data.data?.new_entity_id,
|
||||
source: props.source,
|
||||
...extractTemplateActionCommonParams(sourceProduct),
|
||||
});
|
||||
props?.onSuccess?.({
|
||||
originProductId: inputParam?.productId ?? '',
|
||||
newEntityId: data.data?.new_entity_id ?? '',
|
||||
spaceId: inputParam?.spaceId ?? '',
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
modalContextHolder: (
|
||||
<ProjectTemplateCopyModal
|
||||
title={props.modalTitle}
|
||||
isSelectSpace={props.isSelectSpace}
|
||||
visible={visible}
|
||||
okButtonProps={{
|
||||
disabled: !isFormValid,
|
||||
loading,
|
||||
}}
|
||||
onOk={async () => {
|
||||
const val = await formApi.current?.validate();
|
||||
if (val) {
|
||||
run(val);
|
||||
}
|
||||
}}
|
||||
onCancel={onModalClose}
|
||||
formProps={{
|
||||
initValues,
|
||||
onValueChange: val => {
|
||||
// 当用户删除 input 中所有字符时,val.name 字段会消失,而不是空字符串,神秘
|
||||
setIsFormValid(!!val.name?.trim());
|
||||
},
|
||||
getFormApi: api => {
|
||||
formApi.current = api;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
),
|
||||
copyProject: ({
|
||||
initValue,
|
||||
sourceProduct: inputSourceProduct,
|
||||
}: {
|
||||
initValue: ProjectTemplateCopyValue;
|
||||
/** 用于提取埋点参数 */
|
||||
sourceProduct: ProductInfo;
|
||||
}) => {
|
||||
setInitValues({
|
||||
...initValue,
|
||||
name: botInputLengthService.sliceStringByMaxLength({
|
||||
value: appendCopySuffix(initValue.name),
|
||||
field: 'projectName',
|
||||
}),
|
||||
});
|
||||
setSourceProduct(inputSourceProduct);
|
||||
setVisible(true);
|
||||
setIsFormValid(!!initValue?.name?.trim());
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export { appendCopySuffix };
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 { I18n } from '@coze-arch/i18n';
|
||||
|
||||
export const appendCopySuffix = (name: string) =>
|
||||
`${name}(${I18n.t('duplicate_rename_copy')})`;
|
||||
@@ -0,0 +1,9 @@
|
||||
.handle {
|
||||
width: 1px;
|
||||
background-color: var(--coz-stroke-primary);
|
||||
}
|
||||
|
||||
.hot-zone:hover .handle, .handle-moving {
|
||||
width: 4px;
|
||||
background-color: var(--coz-stroke-hglt);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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 MouseEventHandler,
|
||||
type FC,
|
||||
useRef,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import classnames from 'classnames';
|
||||
|
||||
import s from './handle.module.less';
|
||||
|
||||
// 目前只支持水平方向,按需扩展吧
|
||||
export interface ResizableLayoutHandleProps {
|
||||
className?: string;
|
||||
hotZoneClassName?: string;
|
||||
onMove: (offset: number) => void;
|
||||
onMoveStart: () => void;
|
||||
onMoveEnd: () => void;
|
||||
}
|
||||
|
||||
interface HandleState {
|
||||
startX: number;
|
||||
moving: boolean;
|
||||
}
|
||||
|
||||
const hotZoneStyle = classnames(
|
||||
s['hot-zone'],
|
||||
'flex items-stretch justify-center',
|
||||
'cursor-col-resize',
|
||||
'z-10',
|
||||
'w-[8px] mx-[-3.5px]',
|
||||
'bg-transparent',
|
||||
);
|
||||
|
||||
const handleStyle = classnames('transition-width duration-300 ease-in-out');
|
||||
|
||||
export const ResizableLayoutHandle: FC<ResizableLayoutHandleProps> = ({
|
||||
className,
|
||||
hotZoneClassName,
|
||||
onMove,
|
||||
onMoveStart,
|
||||
onMoveEnd,
|
||||
}) => {
|
||||
const [moving, setMoving] = useState(false);
|
||||
const stateRef = useRef<HandleState>({
|
||||
startX: 0,
|
||||
moving: false,
|
||||
});
|
||||
|
||||
const callbackRef = useRef({
|
||||
onMove,
|
||||
onMoveStart,
|
||||
onMoveEnd,
|
||||
});
|
||||
|
||||
callbackRef.current = {
|
||||
onMove,
|
||||
onMoveStart,
|
||||
onMoveEnd,
|
||||
};
|
||||
|
||||
const moveEnd = useCallback(() => {
|
||||
setMoving(false);
|
||||
stateRef.current = {
|
||||
startX: 0,
|
||||
moving: false,
|
||||
};
|
||||
offEvents();
|
||||
callbackRef.current.onMoveEnd();
|
||||
}, []);
|
||||
|
||||
const move = useCallback((e: PointerEvent) => {
|
||||
if (stateRef.current.moving) {
|
||||
callbackRef.current.onMove(e.clientX - stateRef.current.startX);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const offEvents = () => {
|
||||
window.removeEventListener('pointermove', move, false);
|
||||
// 适配移动端出现多点触控的情况
|
||||
window.removeEventListener('pointerdown', moveEnd, false);
|
||||
window.removeEventListener('pointerup', moveEnd, false);
|
||||
window.removeEventListener('pointercancel', moveEnd, false);
|
||||
};
|
||||
|
||||
const onMouseDown: MouseEventHandler<HTMLDivElement> = e => {
|
||||
stateRef.current = {
|
||||
moving: true,
|
||||
startX: e.pageX,
|
||||
};
|
||||
setMoving(true);
|
||||
callbackRef.current.onMoveStart();
|
||||
window.addEventListener('pointermove', move, false);
|
||||
// 适配移动端出现多点触控的情况
|
||||
window.addEventListener('pointerdown', moveEnd, false);
|
||||
window.addEventListener('pointerup', moveEnd, false);
|
||||
window.addEventListener('pointercancel', moveEnd, false);
|
||||
};
|
||||
// TODO hover 样式 & 热区宽度需要和 UI 对齐
|
||||
return (
|
||||
<div
|
||||
className={classnames(hotZoneStyle, hotZoneClassName)}
|
||||
onMouseDown={onMouseDown}
|
||||
>
|
||||
<div
|
||||
className={classnames(
|
||||
className,
|
||||
s.handle,
|
||||
moving && s['handle-moving'],
|
||||
handleStyle,
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ResizableLayoutHandle.displayName = 'ResizableLayoutHandle';
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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 {
|
||||
Children,
|
||||
type PropsWithChildren,
|
||||
useRef,
|
||||
type FC,
|
||||
isValidElement,
|
||||
cloneElement,
|
||||
type ReactNode,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { sum } from 'lodash-es';
|
||||
import classnames from 'classnames';
|
||||
import { useDebounceEffect, useSize } from 'ahooks';
|
||||
|
||||
import { type ResizableLayoutProps } from './types';
|
||||
import { ResizableLayoutHandle } from './handle';
|
||||
|
||||
interface LayoutState {
|
||||
moving: boolean;
|
||||
itemWidth: number[];
|
||||
}
|
||||
|
||||
const getDefaultState = () => ({
|
||||
moving: false,
|
||||
itemWidth: [],
|
||||
});
|
||||
|
||||
export const ResizableLayout: FC<PropsWithChildren<ResizableLayoutProps>> = ({
|
||||
className,
|
||||
children,
|
||||
handleClassName,
|
||||
hotZoneClassName,
|
||||
}) => {
|
||||
const [state, setState] = useState<LayoutState>(getDefaultState());
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const childRef = useRef<HTMLElement[]>([]);
|
||||
|
||||
const size = useSize(containerRef);
|
||||
|
||||
useDebounceEffect(
|
||||
() => {
|
||||
if (!size?.width) {
|
||||
return;
|
||||
}
|
||||
const totalSize = sum(state.itemWidth);
|
||||
// 排除还没有进行过拖拽的情况,此时本地 state 中没有记录上次分配的宽度
|
||||
if (totalSize <= 0) {
|
||||
return;
|
||||
}
|
||||
const ratio = size.width / totalSize;
|
||||
const newItemWidth = state.itemWidth.map(w => w * ratio);
|
||||
childRef.current.forEach(
|
||||
(item, index) => (item.style.width = `${newItemWidth[index]}px`),
|
||||
);
|
||||
setState({
|
||||
...state,
|
||||
itemWidth: newItemWidth,
|
||||
});
|
||||
},
|
||||
[size?.width],
|
||||
{
|
||||
wait: 20,
|
||||
maxWait: 100,
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(
|
||||
'flex w-full items-stretch',
|
||||
className,
|
||||
state.moving && 'cursor-col-resize select-none',
|
||||
)}
|
||||
ref={containerRef}
|
||||
>
|
||||
{Children.map(children, (child, index) => {
|
||||
let node: ReactNode;
|
||||
if (isValidElement(child)) {
|
||||
node = cloneElement(
|
||||
child,
|
||||
Object.assign({}, child.props, {
|
||||
ref: (target: React.ReactNode) => {
|
||||
if (target instanceof HTMLElement) {
|
||||
childRef.current[index] = target;
|
||||
} else {
|
||||
if (!IS_PROD && target) {
|
||||
throw Error(
|
||||
'children of ResizableLayout need a ref of HTMLElement',
|
||||
);
|
||||
}
|
||||
}
|
||||
// @ts-expect-error -- 跳过类型体操
|
||||
const { ref } = child;
|
||||
if (typeof ref === 'function') {
|
||||
ref(target);
|
||||
} else if (ref && typeof ref === 'object') {
|
||||
ref.current = target;
|
||||
}
|
||||
},
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
node = (
|
||||
<div
|
||||
ref={elm => {
|
||||
if (elm) {
|
||||
childRef.current[index] = elm;
|
||||
}
|
||||
}}
|
||||
>
|
||||
{child}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{index > 0 && (
|
||||
<ResizableLayoutHandle
|
||||
className={handleClassName}
|
||||
hotZoneClassName={hotZoneClassName}
|
||||
onMoveStart={() => {
|
||||
setState({
|
||||
moving: true,
|
||||
itemWidth: childRef.current.map(
|
||||
item => item.clientWidth ?? 0,
|
||||
),
|
||||
});
|
||||
}}
|
||||
// 相对于初始位置的偏移量
|
||||
onMove={offset => {
|
||||
const pre = index - 1;
|
||||
childRef.current[pre].style.width = `${
|
||||
state.itemWidth[pre] + offset
|
||||
}px`;
|
||||
childRef.current[index].style.width = `${
|
||||
state.itemWidth[index] - offset
|
||||
}px`;
|
||||
}}
|
||||
onMoveEnd={() => {
|
||||
setState({
|
||||
// 拖拽结束后,记录真实宽度
|
||||
itemWidth: childRef.current.map(
|
||||
item => item.clientWidth ?? 0,
|
||||
),
|
||||
moving: false,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{node}
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface ResizableLayoutProps {
|
||||
className?: string;
|
||||
handleClassName?: string;
|
||||
hotZoneClassName?: string;
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
export function BotNoResult(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="280"
|
||||
height="102"
|
||||
viewBox="0 0 280 102"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="280" height="102" rx="8" fill="#F9F9F9" />
|
||||
<rect
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
width="279"
|
||||
height="101"
|
||||
rx="7.5"
|
||||
stroke="url(#paint0_linear_4513_102766)"
|
||||
strokeOpacity="0.24"
|
||||
/>
|
||||
<rect
|
||||
opacity="0.12"
|
||||
x="16"
|
||||
y="18"
|
||||
width="104"
|
||||
height="14"
|
||||
rx="7"
|
||||
fill="#2B3245"
|
||||
/>
|
||||
<rect
|
||||
opacity="0.12"
|
||||
x="16"
|
||||
y="40"
|
||||
width="196"
|
||||
height="14"
|
||||
rx="7"
|
||||
fill="#2B3245"
|
||||
/>
|
||||
<g filter="url(#filter0_d_4513_102766)">
|
||||
<path
|
||||
d="M224 22.4C224 20.1598 224 19.0397 224.436 18.184C224.819 17.4314 225.431 16.8195 226.184 16.436C227.04 16 228.16 16 230.4 16H257.6C259.84 16 260.96 16 261.816 16.436C262.569 16.8195 263.181 17.4314 263.564 18.184C264 19.0397 264 20.1598 264 22.4V49.6C264 51.8402 264 52.9603 263.564 53.816C263.181 54.5686 262.569 55.1805 261.816 55.564C260.96 56 259.84 56 257.6 56H230.4C228.16 56 227.04 56 226.184 55.564C225.431 55.1805 224.819 54.5686 224.436 53.816C224 52.9603 224 51.8402 224 49.6V22.4Z"
|
||||
fill="url(#paint1_linear_4513_102766)"
|
||||
/>
|
||||
<path
|
||||
d="M224.1 22.4C224.1 21.2782 224.1 20.4429 224.154 19.781C224.208 19.1201 224.315 18.6408 224.525 18.2294C224.899 17.4956 225.496 16.899 226.229 16.5251C226.641 16.3155 227.12 16.2082 227.781 16.1542C228.443 16.1001 229.278 16.1 230.4 16.1H257.6C258.722 16.1 259.557 16.1001 260.219 16.1542C260.88 16.2082 261.359 16.3155 261.771 16.5251C262.504 16.899 263.101 17.4956 263.475 18.2294C263.685 18.6408 263.792 19.1201 263.846 19.781C263.9 20.4429 263.9 21.2782 263.9 22.4V49.6C263.9 50.7218 263.9 51.5571 263.846 52.219C263.792 52.8799 263.685 53.3592 263.475 53.7706C263.101 54.5044 262.504 55.101 261.771 55.4749C261.359 55.6845 260.88 55.7918 260.219 55.8458C259.557 55.8999 258.722 55.9 257.6 55.9H230.4C229.278 55.9 228.443 55.8999 227.781 55.8458C227.12 55.7918 226.641 55.6845 226.229 55.4749C225.496 55.101 224.899 54.5044 224.525 53.7706C224.315 53.3592 224.208 52.8799 224.154 52.219C224.1 51.5571 224.1 50.7218 224.1 49.6V22.4Z"
|
||||
stroke="black"
|
||||
strokeOpacity="0.08"
|
||||
strokeWidth="0.2"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
opacity="0.12"
|
||||
x="16"
|
||||
y="72"
|
||||
width="248"
|
||||
height="14"
|
||||
rx="7"
|
||||
fill="#2B3245"
|
||||
/>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_4513_102766"
|
||||
x="215.429"
|
||||
y="10.2857"
|
||||
width="57.1429"
|
||||
height="57.1429"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2.85714" />
|
||||
<feGaussianBlur stdDeviation="4.28571" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_4513_102766"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_4513_102766"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_4513_102766"
|
||||
x1="140"
|
||||
y1="0"
|
||||
x2="140"
|
||||
y2="102"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#38415A" />
|
||||
<stop offset="1" stopColor="#2B3245" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_4513_102766"
|
||||
x1="244"
|
||||
y1="16"
|
||||
x2="244"
|
||||
y2="56"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#F45D68" />
|
||||
<stop offset="1" stopColor="#FFCA00" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
export function BotNoResultDark(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="280"
|
||||
height="102"
|
||||
viewBox="0 0 280 102"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="0.5" y="0.5" width="279" height="101" rx="7.5" fill="#1C2333" />
|
||||
<rect
|
||||
x="0.5"
|
||||
y="0.5"
|
||||
width="279"
|
||||
height="101"
|
||||
rx="7.5"
|
||||
stroke="url(#paint0_linear_4513_103137)"
|
||||
/>
|
||||
<rect x="16" y="18" width="104" height="14" rx="7" fill="#2B3245" />
|
||||
<rect x="16" y="40" width="196" height="14" rx="7" fill="#2B3245" />
|
||||
<g filter="url(#filter0_d_4513_103137)">
|
||||
<path
|
||||
d="M224 22.4C224 20.1598 224 19.0397 224.436 18.184C224.819 17.4314 225.431 16.8195 226.184 16.436C227.04 16 228.16 16 230.4 16H257.6C259.84 16 260.96 16 261.816 16.436C262.569 16.8195 263.181 17.4314 263.564 18.184C264 19.0397 264 20.1598 264 22.4V49.6C264 51.8402 264 52.9603 263.564 53.816C263.181 54.5686 262.569 55.1805 261.816 55.564C260.96 56 259.84 56 257.6 56H230.4C228.16 56 227.04 56 226.184 55.564C225.431 55.1805 224.819 54.5686 224.436 53.816C224 52.9603 224 51.8402 224 49.6V22.4Z"
|
||||
fill="url(#paint1_linear_4513_103137)"
|
||||
/>
|
||||
<path
|
||||
d="M224.1 22.4C224.1 21.2782 224.1 20.4429 224.154 19.781C224.208 19.1201 224.315 18.6408 224.525 18.2294C224.899 17.4956 225.496 16.899 226.229 16.5251C226.641 16.3155 227.12 16.2082 227.781 16.1542C228.443 16.1001 229.278 16.1 230.4 16.1H257.6C258.722 16.1 259.557 16.1001 260.219 16.1542C260.88 16.2082 261.359 16.3155 261.771 16.5251C262.504 16.899 263.101 17.4956 263.475 18.2294C263.685 18.6408 263.792 19.1201 263.846 19.781C263.9 20.4429 263.9 21.2782 263.9 22.4V49.6C263.9 50.7218 263.9 51.5571 263.846 52.219C263.792 52.8799 263.685 53.3592 263.475 53.7706C263.101 54.5044 262.504 55.101 261.771 55.4749C261.359 55.6845 260.88 55.7918 260.219 55.8458C259.557 55.8999 258.722 55.9 257.6 55.9H230.4C229.278 55.9 228.443 55.8999 227.781 55.8458C227.12 55.7918 226.641 55.6845 226.229 55.4749C225.496 55.101 224.899 54.5044 224.525 53.7706C224.315 53.3592 224.208 52.8799 224.154 52.219C224.1 51.5571 224.1 50.7218 224.1 49.6V22.4Z"
|
||||
stroke="black"
|
||||
strokeOpacity="0.08"
|
||||
strokeWidth="0.2"
|
||||
/>
|
||||
</g>
|
||||
<rect x="16" y="72" width="248" height="14" rx="7" fill="#2B3245" />
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_4513_103137"
|
||||
x="215.429"
|
||||
y="10.2857"
|
||||
width="57.1429"
|
||||
height="57.1429"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2.85714" />
|
||||
<feGaussianBlur stdDeviation="4.28571" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_4513_103137"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_4513_103137"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_4513_103137"
|
||||
x1="140"
|
||||
y1="0"
|
||||
x2="140"
|
||||
y2="102"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#38415A" />
|
||||
<stop offset="1" stopColor="#2B3245" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_4513_103137"
|
||||
x1="244"
|
||||
y1="16"
|
||||
x2="244"
|
||||
y2="56"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#F45D68" />
|
||||
<stop offset="1" stopColor="#FFCA00" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 { ThemeFactory, type COZTheme } from '../../factory';
|
||||
import { BotNoResultDark } from './BotNoResultDark';
|
||||
import { BotNoResult } from './BotNoResult';
|
||||
export function BotSearchNoCard({ theme }: COZTheme) {
|
||||
return (
|
||||
<ThemeFactory
|
||||
theme={theme}
|
||||
components={{
|
||||
dark: <BotNoResultDark />,
|
||||
light: <BotNoResult />,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function
|
||||
export function CommonNoResult(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="360"
|
||||
height="248"
|
||||
viewBox="0 0 360 248"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="80" width="280" height="72" rx="8" fill="white" />
|
||||
<rect
|
||||
x="80.5"
|
||||
y="0.5"
|
||||
width="279"
|
||||
height="71"
|
||||
rx="7.5"
|
||||
stroke="url(#paint0_linear_4434_146978)"
|
||||
stopOpacity="0.24"
|
||||
/>
|
||||
<g filter="url(#filter0_d_4434_146978)">
|
||||
<path
|
||||
d="M96 28.8C96 24.3196 96 22.0794 96.8719 20.3681C97.6389 18.8628 98.8628 17.6389 100.368 16.8719C102.079 16 104.32 16 108.8 16H123.2C127.68 16 129.921 16 131.632 16.8719C133.137 17.6389 134.361 18.8628 135.128 20.3681C136 22.0794 136 24.3196 136 28.8V43.2C136 47.6804 136 49.9206 135.128 51.6319C134.361 53.1372 133.137 54.3611 131.632 55.1281C129.921 56 127.68 56 123.2 56H108.8C104.32 56 102.079 56 100.368 55.1281C98.8628 54.3611 97.6389 53.1372 96.8719 51.6319C96 49.9206 96 47.6804 96 43.2V28.8Z"
|
||||
fill="url(#paint1_linear_4434_146978)"
|
||||
/>
|
||||
<path
|
||||
d="M96.1 28.8C96.1 26.5581 96.1001 24.8828 96.2087 23.5538C96.3171 22.2259 96.5335 21.2527 96.961 20.4135C97.7184 18.927 98.927 17.7184 100.413 16.961C101.253 16.5335 102.226 16.3171 103.554 16.2087C104.883 16.1001 106.558 16.1 108.8 16.1H123.2C125.442 16.1 127.117 16.1001 128.446 16.2087C129.774 16.3171 130.747 16.5335 131.587 16.961C133.073 17.7184 134.282 18.927 135.039 20.4135C135.467 21.2527 135.683 22.2259 135.791 23.5538C135.9 24.8828 135.9 26.5581 135.9 28.8V43.2C135.9 45.4419 135.9 47.1172 135.791 48.4462C135.683 49.7741 135.467 50.7473 135.039 51.5865C134.282 53.073 133.073 54.2816 131.587 55.039C130.747 55.4665 129.774 55.6828 128.446 55.7913C127.117 55.8999 125.442 55.9 123.2 55.9H108.8C106.558 55.9 104.883 55.8999 103.554 55.7913C102.226 55.6828 101.253 55.4665 100.413 55.039C98.927 54.2816 97.7184 53.073 96.961 51.5865C96.5335 50.7473 96.3171 49.7741 96.2087 48.4462C96.1001 47.1172 96.1 45.4419 96.1 43.2V28.8Z"
|
||||
stopOpacity="0.08"
|
||||
strokeWidth="0.2"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
opacity="0.12"
|
||||
x="148"
|
||||
y="18"
|
||||
width="120"
|
||||
height="14"
|
||||
rx="7"
|
||||
fill="#2B3245"
|
||||
/>
|
||||
<rect
|
||||
opacity="0.12"
|
||||
x="148"
|
||||
y="40"
|
||||
width="196"
|
||||
height="14"
|
||||
rx="7"
|
||||
fill="#2B3245"
|
||||
/>
|
||||
<rect y="88" width="280" height="72" rx="8" fill="white" />
|
||||
<rect
|
||||
x="0.5"
|
||||
y="88.5"
|
||||
width="279"
|
||||
height="71"
|
||||
rx="7.5"
|
||||
stroke="url(#paint2_linear_4434_146978)"
|
||||
stopOpacity="0.24"
|
||||
/>
|
||||
<g filter="url(#filter1_d_4434_146978)">
|
||||
<path
|
||||
d="M16 116.8C16 112.32 16 110.079 16.8719 108.368C17.6389 106.863 18.8628 105.639 20.3681 104.872C22.0794 104 24.3196 104 28.8 104H43.2C47.6804 104 49.9206 104 51.6319 104.872C53.1372 105.639 54.3611 106.863 55.1281 108.368C56 110.079 56 112.32 56 116.8V131.2C56 135.68 56 137.921 55.1281 139.632C54.3611 141.137 53.1372 142.361 51.6319 143.128C49.9206 144 47.6804 144 43.2 144H28.8C24.3196 144 22.0794 144 20.3681 143.128C18.8628 142.361 17.6389 141.137 16.8719 139.632C16 137.921 16 135.68 16 131.2V116.8Z"
|
||||
fill="url(#paint3_linear_4434_146978)"
|
||||
/>
|
||||
<path
|
||||
d="M16.1 116.8C16.1 114.558 16.1001 112.883 16.2087 111.554C16.3171 110.226 16.5335 109.253 16.961 108.413C17.7184 106.927 18.927 105.718 20.4135 104.961C21.2527 104.533 22.2259 104.317 23.5538 104.209C24.8828 104.1 26.5581 104.1 28.8 104.1H43.2C45.4419 104.1 47.1172 104.1 48.4462 104.209C49.7741 104.317 50.7473 104.533 51.5865 104.961C53.073 105.718 54.2816 106.927 55.039 108.413C55.4665 109.253 55.6828 110.226 55.7913 111.554C55.8999 112.883 55.9 114.558 55.9 116.8V131.2C55.9 133.442 55.8999 135.117 55.7913 136.446C55.6828 137.774 55.4665 138.747 55.039 139.587C54.2816 141.073 53.073 142.282 51.5865 143.039C50.7473 143.467 49.7741 143.683 48.4462 143.791C47.1172 143.9 45.4419 143.9 43.2 143.9H28.8C26.5581 143.9 24.8828 143.9 23.5538 143.791C22.2259 143.683 21.2527 143.467 20.4135 143.039C18.927 142.282 17.7184 141.073 16.961 139.587C16.5335 138.747 16.3171 137.774 16.2087 136.446C16.1001 135.117 16.1 133.442 16.1 131.2V116.8Z"
|
||||
stopOpacity="0.08"
|
||||
strokeWidth="0.2"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
opacity="0.12"
|
||||
x="68"
|
||||
y="106"
|
||||
width="120"
|
||||
height="14"
|
||||
rx="7"
|
||||
fill="#2B3245"
|
||||
/>
|
||||
<rect
|
||||
opacity="0.12"
|
||||
x="68"
|
||||
y="128"
|
||||
width="196"
|
||||
height="14"
|
||||
rx="7"
|
||||
fill="#2B3245"
|
||||
/>
|
||||
<rect x="80" y="176" width="280" height="72" rx="8" fill="white" />
|
||||
<rect
|
||||
x="80.5"
|
||||
y="176.5"
|
||||
width="279"
|
||||
height="71"
|
||||
rx="7.5"
|
||||
stroke="url(#paint4_linear_4434_146978)"
|
||||
stopOpacity="0.24"
|
||||
/>
|
||||
<g filter="url(#filter2_d_4434_146978)">
|
||||
<path
|
||||
d="M96 204.8C96 200.32 96 198.079 96.8719 196.368C97.6389 194.863 98.8628 193.639 100.368 192.872C102.079 192 104.32 192 108.8 192H123.2C127.68 192 129.921 192 131.632 192.872C133.137 193.639 134.361 194.863 135.128 196.368C136 198.079 136 200.32 136 204.8V219.2C136 223.68 136 225.921 135.128 227.632C134.361 229.137 133.137 230.361 131.632 231.128C129.921 232 127.68 232 123.2 232H108.8C104.32 232 102.079 232 100.368 231.128C98.8628 230.361 97.6389 229.137 96.8719 227.632C96 225.921 96 223.68 96 219.2V204.8Z"
|
||||
fill="url(#paint5_linear_4434_146978)"
|
||||
/>
|
||||
<path
|
||||
d="M96.1 204.8C96.1 202.558 96.1001 200.883 96.2087 199.554C96.3171 198.226 96.5335 197.253 96.961 196.413C97.7184 194.927 98.927 193.718 100.413 192.961C101.253 192.533 102.226 192.317 103.554 192.209C104.883 192.1 106.558 192.1 108.8 192.1H123.2C125.442 192.1 127.117 192.1 128.446 192.209C129.774 192.317 130.747 192.533 131.587 192.961C133.073 193.718 134.282 194.927 135.039 196.413C135.467 197.253 135.683 198.226 135.791 199.554C135.9 200.883 135.9 202.558 135.9 204.8V219.2C135.9 221.442 135.9 223.117 135.791 224.446C135.683 225.774 135.467 226.747 135.039 227.587C134.282 229.073 133.073 230.282 131.587 231.039C130.747 231.467 129.774 231.683 128.446 231.791C127.117 231.9 125.442 231.9 123.2 231.9H108.8C106.558 231.9 104.883 231.9 103.554 231.791C102.226 231.683 101.253 231.467 100.413 231.039C98.927 230.282 97.7184 229.073 96.961 227.587C96.5335 226.747 96.3171 225.774 96.2087 224.446C96.1001 223.117 96.1 221.442 96.1 219.2V204.8Z"
|
||||
stopOpacity="0.08"
|
||||
strokeWidth="0.2"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
opacity="0.12"
|
||||
x="148"
|
||||
y="193"
|
||||
width="120"
|
||||
height="16"
|
||||
rx="8"
|
||||
fill="#2B3245"
|
||||
/>
|
||||
<rect
|
||||
opacity="0.12"
|
||||
x="148"
|
||||
y="217"
|
||||
width="196"
|
||||
height="14"
|
||||
rx="7"
|
||||
fill="#2B3245"
|
||||
/>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_4434_146978"
|
||||
x="87.4286"
|
||||
y="10.2857"
|
||||
width="57.1429"
|
||||
height="57.1429"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2.85714" />
|
||||
<feGaussianBlur stdDeviation="4.28571" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_4434_146978"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_4434_146978"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_d_4434_146978"
|
||||
x="7.42857"
|
||||
y="98.2857"
|
||||
width="57.1429"
|
||||
height="57.1429"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2.85714" />
|
||||
<feGaussianBlur stdDeviation="4.28571" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_4434_146978"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_4434_146978"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_d_4434_146978"
|
||||
x="87.4286"
|
||||
y="186.286"
|
||||
width="57.1429"
|
||||
height="57.1429"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2.85714" />
|
||||
<feGaussianBlur stdDeviation="4.28571" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_4434_146978"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_4434_146978"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_4434_146978"
|
||||
x1="220"
|
||||
y1="0"
|
||||
x2="220"
|
||||
y2="72"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EBEBED" />
|
||||
<stop offset="1" stopColor="#CFDBD7" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_4434_146978"
|
||||
x1="116"
|
||||
y1="16"
|
||||
x2="116"
|
||||
y2="56"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#F45D68" />
|
||||
<stop offset="1" stopColor="#FFCA00" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_4434_146978"
|
||||
x1="140"
|
||||
y1="88"
|
||||
x2="140"
|
||||
y2="160"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EBEBED" />
|
||||
<stop offset="1" stopColor="#CFDBD7" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_4434_146978"
|
||||
x1="36"
|
||||
y1="104"
|
||||
x2="36"
|
||||
y2="144"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#F45D68" />
|
||||
<stop offset="0.0001" stopColor="#5CBCB0" />
|
||||
<stop offset="1" stopColor="#FAC818" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint4_linear_4434_146978"
|
||||
x1="220"
|
||||
y1="176"
|
||||
x2="220"
|
||||
y2="248"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#EBEBED" />
|
||||
<stop offset="1" stopColor="#CFDBD7" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint5_linear_4434_146978"
|
||||
x1="116"
|
||||
y1="192"
|
||||
x2="116"
|
||||
y2="232"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#3D8DF3" />
|
||||
<stop offset="0.0001" stopColor="#5CBCB0" />
|
||||
<stop offset="1" stopColor="#133BFE" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function
|
||||
export function CommonNoResultDark(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="360"
|
||||
height="248"
|
||||
viewBox="0 0 360 248"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="80.5" y="0.5" width="279" height="71" rx="7.5" fill="#1C2333" />
|
||||
<rect
|
||||
x="80.5"
|
||||
y="0.5"
|
||||
width="279"
|
||||
height="71"
|
||||
rx="7.5"
|
||||
stroke="url(#paint0_linear_116_6085)"
|
||||
/>
|
||||
<g filter="url(#filter0_d_116_6085)">
|
||||
<path
|
||||
d="M96 28.8C96 24.3196 96 22.0794 96.8719 20.3681C97.6389 18.8628 98.8628 17.6389 100.368 16.8719C102.079 16 104.32 16 108.8 16H123.2C127.68 16 129.921 16 131.632 16.8719C133.137 17.6389 134.361 18.8628 135.128 20.3681C136 22.0794 136 24.3196 136 28.8V43.2C136 47.6804 136 49.9206 135.128 51.6319C134.361 53.1372 133.137 54.3611 131.632 55.1281C129.921 56 127.68 56 123.2 56H108.8C104.32 56 102.079 56 100.368 55.1281C98.8628 54.3611 97.6389 53.1372 96.8719 51.6319C96 49.9206 96 47.6804 96 43.2V28.8Z"
|
||||
fill="url(#paint1_linear_116_6085)"
|
||||
/>
|
||||
<path
|
||||
d="M96.1 28.8C96.1 26.5581 96.1001 24.8828 96.2087 23.5538C96.3171 22.2259 96.5335 21.2527 96.961 20.4135C97.7184 18.927 98.927 17.7184 100.413 16.961C101.253 16.5335 102.226 16.3171 103.554 16.2087C104.883 16.1001 106.558 16.1 108.8 16.1H123.2C125.442 16.1 127.117 16.1001 128.446 16.2087C129.774 16.3171 130.747 16.5335 131.587 16.961C133.073 17.7184 134.282 18.927 135.039 20.4135C135.467 21.2527 135.683 22.2259 135.791 23.5538C135.9 24.8828 135.9 26.5581 135.9 28.8V43.2C135.9 45.4419 135.9 47.1172 135.791 48.4462C135.683 49.7741 135.467 50.7473 135.039 51.5865C134.282 53.073 133.073 54.2816 131.587 55.039C130.747 55.4665 129.774 55.6828 128.446 55.7913C127.117 55.8999 125.442 55.9 123.2 55.9H108.8C106.558 55.9 104.883 55.8999 103.554 55.7913C102.226 55.6828 101.253 55.4665 100.413 55.039C98.927 54.2816 97.7184 53.073 96.961 51.5865C96.5335 50.7473 96.3171 49.7741 96.2087 48.4462C96.1001 47.1172 96.1 45.4419 96.1 43.2V28.8Z"
|
||||
stroke="black"
|
||||
stopOpacity="0.08"
|
||||
strokeWidth="0.2"
|
||||
/>
|
||||
</g>
|
||||
<rect x="148" y="18" width="120" height="14" rx="7" fill="#2B3245" />
|
||||
<rect x="148" y="40" width="196" height="14" rx="7" fill="#2B3245" />
|
||||
<rect x="0.5" y="88.5" width="279" height="71" rx="7.5" fill="#1C2333" />
|
||||
<rect
|
||||
x="0.5"
|
||||
y="88.5"
|
||||
width="279"
|
||||
height="71"
|
||||
rx="7.5"
|
||||
stroke="url(#paint2_linear_116_6085)"
|
||||
/>
|
||||
<g filter="url(#filter1_d_116_6085)">
|
||||
<path
|
||||
d="M16 116.8C16 112.32 16 110.079 16.8719 108.368C17.6389 106.863 18.8628 105.639 20.3681 104.872C22.0794 104 24.3196 104 28.8 104H43.2C47.6804 104 49.9206 104 51.6319 104.872C53.1372 105.639 54.3611 106.863 55.1281 108.368C56 110.079 56 112.32 56 116.8V131.2C56 135.68 56 137.921 55.1281 139.632C54.3611 141.137 53.1372 142.361 51.6319 143.128C49.9206 144 47.6804 144 43.2 144H28.8C24.3196 144 22.0794 144 20.3681 143.128C18.8628 142.361 17.6389 141.137 16.8719 139.632C16 137.921 16 135.68 16 131.2V116.8Z"
|
||||
fill="url(#paint3_linear_116_6085)"
|
||||
/>
|
||||
<path
|
||||
d="M16.1 116.8C16.1 114.558 16.1001 112.883 16.2087 111.554C16.3171 110.226 16.5335 109.253 16.961 108.413C17.7184 106.927 18.927 105.718 20.4135 104.961C21.2527 104.533 22.2259 104.317 23.5538 104.209C24.8828 104.1 26.5581 104.1 28.8 104.1H43.2C45.4419 104.1 47.1172 104.1 48.4462 104.209C49.7741 104.317 50.7473 104.533 51.5865 104.961C53.073 105.718 54.2816 106.927 55.039 108.413C55.4665 109.253 55.6828 110.226 55.7913 111.554C55.8999 112.883 55.9 114.558 55.9 116.8V131.2C55.9 133.442 55.8999 135.117 55.7913 136.446C55.6828 137.774 55.4665 138.747 55.039 139.587C54.2816 141.073 53.073 142.282 51.5865 143.039C50.7473 143.467 49.7741 143.683 48.4462 143.791C47.1172 143.9 45.4419 143.9 43.2 143.9H28.8C26.5581 143.9 24.8828 143.9 23.5538 143.791C22.2259 143.683 21.2527 143.467 20.4135 143.039C18.927 142.282 17.7184 141.073 16.961 139.587C16.5335 138.747 16.3171 137.774 16.2087 136.446C16.1001 135.117 16.1 133.442 16.1 131.2V116.8Z"
|
||||
stroke="black"
|
||||
stopOpacity="0.08"
|
||||
strokeWidth="0.2"
|
||||
/>
|
||||
</g>
|
||||
<rect x="68" y="106" width="120" height="14" rx="7" fill="#2B3245" />
|
||||
<rect x="68" y="128" width="196" height="14" rx="7" fill="#2B3245" />
|
||||
<rect
|
||||
x="80.5"
|
||||
y="176.5"
|
||||
width="279"
|
||||
height="71"
|
||||
rx="7.5"
|
||||
fill="#1C2333"
|
||||
/>
|
||||
<rect
|
||||
x="80.5"
|
||||
y="176.5"
|
||||
width="279"
|
||||
height="71"
|
||||
rx="7.5"
|
||||
stroke="url(#paint4_linear_116_6085)"
|
||||
/>
|
||||
<g filter="url(#filter2_d_116_6085)">
|
||||
<path
|
||||
d="M96 204.8C96 200.32 96 198.079 96.8719 196.368C97.6389 194.863 98.8628 193.639 100.368 192.872C102.079 192 104.32 192 108.8 192H123.2C127.68 192 129.921 192 131.632 192.872C133.137 193.639 134.361 194.863 135.128 196.368C136 198.079 136 200.32 136 204.8V219.2C136 223.68 136 225.921 135.128 227.632C134.361 229.137 133.137 230.361 131.632 231.128C129.921 232 127.68 232 123.2 232H108.8C104.32 232 102.079 232 100.368 231.128C98.8628 230.361 97.6389 229.137 96.8719 227.632C96 225.921 96 223.68 96 219.2V204.8Z"
|
||||
fill="url(#paint5_linear_116_6085)"
|
||||
/>
|
||||
<path
|
||||
d="M96.1 204.8C96.1 202.558 96.1001 200.883 96.2087 199.554C96.3171 198.226 96.5335 197.253 96.961 196.413C97.7184 194.927 98.927 193.718 100.413 192.961C101.253 192.533 102.226 192.317 103.554 192.209C104.883 192.1 106.558 192.1 108.8 192.1H123.2C125.442 192.1 127.117 192.1 128.446 192.209C129.774 192.317 130.747 192.533 131.587 192.961C133.073 193.718 134.282 194.927 135.039 196.413C135.467 197.253 135.683 198.226 135.791 199.554C135.9 200.883 135.9 202.558 135.9 204.8V219.2C135.9 221.442 135.9 223.117 135.791 224.446C135.683 225.774 135.467 226.747 135.039 227.587C134.282 229.073 133.073 230.282 131.587 231.039C130.747 231.467 129.774 231.683 128.446 231.791C127.117 231.9 125.442 231.9 123.2 231.9H108.8C106.558 231.9 104.883 231.9 103.554 231.791C102.226 231.683 101.253 231.467 100.413 231.039C98.927 230.282 97.7184 229.073 96.961 227.587C96.5335 226.747 96.3171 225.774 96.2087 224.446C96.1001 223.117 96.1 221.442 96.1 219.2V204.8Z"
|
||||
stroke="black"
|
||||
stopOpacity="0.08"
|
||||
strokeWidth="0.2"
|
||||
/>
|
||||
</g>
|
||||
<rect x="148" y="193" width="120" height="16" rx="8" fill="#2B3245" />
|
||||
<rect x="148" y="217" width="196" height="14" rx="7" fill="#2B3245" />
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_116_6085"
|
||||
x="87.4286"
|
||||
y="10.2857"
|
||||
width="57.1429"
|
||||
height="57.1429"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2.85714" />
|
||||
<feGaussianBlur stdDeviation="4.28571" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_116_6085"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_116_6085"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_d_116_6085"
|
||||
x="7.42857"
|
||||
y="98.2857"
|
||||
width="57.1429"
|
||||
height="57.1429"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2.85714" />
|
||||
<feGaussianBlur stdDeviation="4.28571" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_116_6085"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_116_6085"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_d_116_6085"
|
||||
x="87.4286"
|
||||
y="186.286"
|
||||
width="57.1429"
|
||||
height="57.1429"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2.85714" />
|
||||
<feGaussianBlur stdDeviation="4.28571" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_116_6085"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_116_6085"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_116_6085"
|
||||
x1="220"
|
||||
y1="0"
|
||||
x2="220"
|
||||
y2="72"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#38415A" />
|
||||
<stop offset="1" stopColor="#2B3245" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_116_6085"
|
||||
x1="116"
|
||||
y1="16"
|
||||
x2="116"
|
||||
y2="56"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#F45D68" />
|
||||
<stop offset="1" stopColor="#FFCA00" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_116_6085"
|
||||
x1="140"
|
||||
y1="88"
|
||||
x2="140"
|
||||
y2="160"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#38415A" />
|
||||
<stop offset="1" stopColor="#2B3245" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_116_6085"
|
||||
x1="36"
|
||||
y1="104"
|
||||
x2="36"
|
||||
y2="144"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#F45D68" />
|
||||
<stop offset="0.0001" stopColor="#5CBCB0" />
|
||||
<stop offset="1" stopColor="#FAC818" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint4_linear_116_6085"
|
||||
x1="220"
|
||||
y1="176"
|
||||
x2="220"
|
||||
y2="248"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#38415A" />
|
||||
<stop offset="1" stopColor="#2B3245" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint5_linear_116_6085"
|
||||
x1="116"
|
||||
y1="192"
|
||||
x2="116"
|
||||
y2="232"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#3D8DF3" />
|
||||
<stop offset="0.0001" stopColor="#5CBCB0" />
|
||||
<stop offset="1" stopColor="#133BFE" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 { ThemeFactory, type COZTheme } from '../../factory';
|
||||
import { CommonNoResultDark } from './CommonNoResultDark';
|
||||
import { CommonNoResult } from './CommonNoResult';
|
||||
export function CommonSearchNoCard({ theme }: COZTheme) {
|
||||
return (
|
||||
<ThemeFactory
|
||||
theme={theme}
|
||||
components={{
|
||||
dark: <CommonNoResultDark />,
|
||||
light: <CommonNoResult />,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
.search-no-card {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
background: transparent;
|
||||
|
||||
> div {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.bot-no-card {
|
||||
width: 280px;
|
||||
height: 102px;
|
||||
}
|
||||
|
||||
.common-no-card {
|
||||
width: 360px;
|
||||
height: 248px;
|
||||
}
|
||||
|
||||
.widget-no-card {
|
||||
width: 280px;
|
||||
height: 184px;
|
||||
}
|
||||
|
||||
.social-no-card {
|
||||
width: 576px;
|
||||
height: 288px;
|
||||
}
|
||||
|
||||
.recommend-no-card {
|
||||
width: 576px;
|
||||
height: 288px;
|
||||
}
|
||||
|
||||
.social-scene-flow-no-card {
|
||||
width: 280px;
|
||||
height: 102px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { type COZTheme } from '../factory';
|
||||
import { WidgetSearchNoCard } from './widget';
|
||||
import { SocialSceneFlowSearchNoCard } from './social-scene-flow';
|
||||
import { SocialSearchNoCard } from './social';
|
||||
import { RecommendSearchNoCard } from './recommend';
|
||||
import { CommonSearchNoCard } from './common';
|
||||
import { BotSearchNoCard } from './bot';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface CardProps extends COZTheme {
|
||||
type:
|
||||
| 'bot'
|
||||
| 'common'
|
||||
| 'widget'
|
||||
| 'social'
|
||||
| 'recommend'
|
||||
| 'social-scene-flow';
|
||||
position: 'top' | 'bottom' | 'center';
|
||||
}
|
||||
|
||||
const renderSearchNoCard = (
|
||||
type: CardProps['type'],
|
||||
theme: CardProps['theme'],
|
||||
) => {
|
||||
switch (type) {
|
||||
case 'bot':
|
||||
return <BotSearchNoCard theme={theme} />;
|
||||
case 'common':
|
||||
return <CommonSearchNoCard theme={theme} />;
|
||||
case 'widget':
|
||||
return <WidgetSearchNoCard theme={theme} />;
|
||||
case 'social':
|
||||
return <SocialSearchNoCard theme={theme} />;
|
||||
case 'recommend':
|
||||
return <RecommendSearchNoCard theme={theme} />;
|
||||
case 'social-scene-flow':
|
||||
return <SocialSceneFlowSearchNoCard theme={theme} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export function SearchNoCard({ type, theme, position }: CardProps) {
|
||||
return (
|
||||
<div
|
||||
className={classnames(s['search-no-card'], {
|
||||
['items-start']: position === 'top',
|
||||
['items-center']: position === 'center',
|
||||
['items-end']: position === 'bottom',
|
||||
})}
|
||||
>
|
||||
<div className={s[`${type}-no-card`]}>
|
||||
{renderSearchNoCard(type, theme)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function
|
||||
export function RecommendNoResult(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="576"
|
||||
height="288"
|
||||
viewBox="0 0 576 288"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect x="148" y="136" width="280" height="152" rx="8" fill="#F9F9F9" />
|
||||
<rect
|
||||
x="148.5"
|
||||
y="136.5"
|
||||
width="279"
|
||||
height="151"
|
||||
rx="7.5"
|
||||
stroke="url(#paint0_linear_7238_190934)"
|
||||
strokeOpacity="0.24"
|
||||
/>
|
||||
<rect
|
||||
opacity="0.12"
|
||||
x="164"
|
||||
y="152"
|
||||
width="104"
|
||||
height="14"
|
||||
rx="7"
|
||||
fill="#2B3245"
|
||||
/>
|
||||
<rect
|
||||
opacity="0.12"
|
||||
x="164"
|
||||
y="174"
|
||||
width="248"
|
||||
height="14"
|
||||
rx="7"
|
||||
fill="#2B3245"
|
||||
/>
|
||||
<rect
|
||||
opacity="0.12"
|
||||
x="164"
|
||||
y="196"
|
||||
width="248"
|
||||
height="14"
|
||||
rx="7"
|
||||
fill="#2B3245"
|
||||
/>
|
||||
<rect
|
||||
opacity="0.12"
|
||||
x="164"
|
||||
y="226"
|
||||
width="120"
|
||||
height="14"
|
||||
rx="7"
|
||||
fill="#2B3245"
|
||||
/>
|
||||
<g filter="url(#filter0_d_7238_190934)">
|
||||
<path
|
||||
d="M164 262.4C164 260.16 164 259.04 164.436 258.184C164.819 257.431 165.431 256.819 166.184 256.436C167.04 256 168.16 256 170.4 256H173.6C175.84 256 176.96 256 177.816 256.436C178.569 256.819 179.181 257.431 179.564 258.184C180 259.04 180 260.16 180 262.4V265.6C180 267.84 180 268.96 179.564 269.816C179.181 270.569 178.569 271.181 177.816 271.564C176.96 272 175.84 272 173.6 272H170.4C168.16 272 167.04 272 166.184 271.564C165.431 271.181 164.819 270.569 164.436 269.816C164 268.96 164 267.84 164 265.6V262.4Z"
|
||||
fill="url(#paint1_linear_7238_190934)"
|
||||
/>
|
||||
<path
|
||||
d="M170.4 272.5H173.6H173.623C174.724 272.5 175.581 272.5 176.268 272.444C176.966 272.387 177.533 272.269 178.043 272.01C178.89 271.578 179.578 270.89 180.01 270.043C180.269 269.533 180.387 268.966 180.444 268.268C180.5 267.581 180.5 266.724 180.5 265.623V265.6V262.4V262.377C180.5 261.276 180.5 260.419 180.444 259.732C180.387 259.034 180.269 258.467 180.01 257.957C179.578 257.11 178.89 256.422 178.043 255.99C177.533 255.731 176.966 255.613 176.268 255.556C175.581 255.5 174.724 255.5 173.623 255.5H173.6H170.4H170.377C169.276 255.5 168.419 255.5 167.732 255.556C167.034 255.613 166.467 255.731 165.957 255.99C165.11 256.422 164.422 257.11 163.99 257.957C163.731 258.467 163.613 259.034 163.556 259.732C163.5 260.419 163.5 261.276 163.5 262.377V262.4V265.6V265.623C163.5 266.724 163.5 267.581 163.556 268.268C163.613 268.966 163.731 269.533 163.99 270.043C164.422 270.89 165.11 271.578 165.957 272.01C166.467 272.269 167.034 272.387 167.732 272.444C168.419 272.5 169.276 272.5 170.377 272.5H170.4Z"
|
||||
stroke="white"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter1_d_7238_190934)">
|
||||
<path
|
||||
d="M176 262.4C176 260.16 176 259.04 176.436 258.184C176.819 257.431 177.431 256.819 178.184 256.436C179.04 256 180.16 256 182.4 256H185.6C187.84 256 188.96 256 189.816 256.436C190.569 256.819 191.181 257.431 191.564 258.184C192 259.04 192 260.16 192 262.4V265.6C192 267.84 192 268.96 191.564 269.816C191.181 270.569 190.569 271.181 189.816 271.564C188.96 272 187.84 272 185.6 272H182.4C180.16 272 179.04 272 178.184 271.564C177.431 271.181 176.819 270.569 176.436 269.816C176 268.96 176 267.84 176 265.6V262.4Z"
|
||||
fill="url(#paint2_linear_7238_190934)"
|
||||
/>
|
||||
<path
|
||||
d="M182.4 272.5H185.6H185.623C186.724 272.5 187.581 272.5 188.268 272.444C188.966 272.387 189.533 272.269 190.043 272.01C190.89 271.578 191.578 270.89 192.01 270.043C192.269 269.533 192.387 268.966 192.444 268.268C192.5 267.581 192.5 266.724 192.5 265.623V265.6V262.4V262.377C192.5 261.276 192.5 260.419 192.444 259.732C192.387 259.034 192.269 258.467 192.01 257.957C191.578 257.11 190.89 256.422 190.043 255.99C189.533 255.731 188.966 255.613 188.268 255.556C187.581 255.5 186.724 255.5 185.623 255.5H185.6H182.4H182.377C181.276 255.5 180.419 255.5 179.732 255.556C179.034 255.613 178.467 255.731 177.957 255.99C177.11 256.422 176.422 257.11 175.99 257.957C175.731 258.467 175.613 259.034 175.556 259.732C175.5 260.419 175.5 261.276 175.5 262.377V262.4V265.6V265.623C175.5 266.724 175.5 267.581 175.556 268.268C175.613 268.966 175.731 269.533 175.99 270.043C176.422 270.89 177.11 271.578 177.957 272.01C178.467 272.269 179.034 272.387 179.732 272.444C180.419 272.5 181.276 272.5 182.377 272.5H182.4Z"
|
||||
stroke="white"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter2_d_7238_190934)">
|
||||
<path
|
||||
d="M188 262.4C188 260.16 188 259.04 188.436 258.184C188.819 257.431 189.431 256.819 190.184 256.436C191.04 256 192.16 256 194.4 256H197.6C199.84 256 200.96 256 201.816 256.436C202.569 256.819 203.181 257.431 203.564 258.184C204 259.04 204 260.16 204 262.4V265.6C204 267.84 204 268.96 203.564 269.816C203.181 270.569 202.569 271.181 201.816 271.564C200.96 272 199.84 272 197.6 272H194.4C192.16 272 191.04 272 190.184 271.564C189.431 271.181 188.819 270.569 188.436 269.816C188 268.96 188 267.84 188 265.6V262.4Z"
|
||||
fill="url(#paint3_linear_7238_190934)"
|
||||
/>
|
||||
<path
|
||||
d="M194.4 272.5H197.6H197.623C198.724 272.5 199.581 272.5 200.268 272.444C200.966 272.387 201.533 272.269 202.043 272.01C202.89 271.578 203.578 270.89 204.01 270.043C204.269 269.533 204.387 268.966 204.444 268.268C204.5 267.581 204.5 266.724 204.5 265.623V265.6V262.4V262.377C204.5 261.276 204.5 260.419 204.444 259.732C204.387 259.034 204.269 258.467 204.01 257.957C203.578 257.11 202.89 256.422 202.043 255.99C201.533 255.731 200.966 255.613 200.268 255.556C199.581 255.5 198.724 255.5 197.623 255.5H197.6H194.4H194.377C193.276 255.5 192.419 255.5 191.732 255.556C191.034 255.613 190.467 255.731 189.957 255.99C189.11 256.422 188.422 257.11 187.99 257.957C187.731 258.467 187.613 259.034 187.556 259.732C187.5 260.419 187.5 261.276 187.5 262.377V262.4V265.6V265.623C187.5 266.724 187.5 267.581 187.556 268.268C187.613 268.966 187.731 269.533 187.99 270.043C188.422 270.89 189.11 271.578 189.957 272.01C190.467 272.269 191.034 272.387 191.732 272.444C192.419 272.5 193.276 272.5 194.377 272.5H194.4Z"
|
||||
stroke="white"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_7238_190934"
|
||||
x="154.429"
|
||||
y="249.286"
|
||||
width="35.1429"
|
||||
height="35.1429"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2.85714" />
|
||||
<feGaussianBlur stdDeviation="4.28571" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_7238_190934"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_7238_190934"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_d_7238_190934"
|
||||
x="166.429"
|
||||
y="249.286"
|
||||
width="35.1429"
|
||||
height="35.1429"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2.85714" />
|
||||
<feGaussianBlur stdDeviation="4.28571" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_7238_190934"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_7238_190934"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_d_7238_190934"
|
||||
x="178.429"
|
||||
y="249.286"
|
||||
width="35.1429"
|
||||
height="35.1429"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2.85714" />
|
||||
<feGaussianBlur stdDeviation="4.28571" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_7238_190934"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_7238_190934"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_7238_190934"
|
||||
x1="288"
|
||||
y1="136"
|
||||
x2="288"
|
||||
y2="288"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#38415A" />
|
||||
<stop offset="1" stopColor="#2B3245" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_7238_190934"
|
||||
x1="172"
|
||||
y1="256"
|
||||
x2="172"
|
||||
y2="272"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#F45D68" />
|
||||
<stop offset="1" stopColor="#FFCA00" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_7238_190934"
|
||||
x1="184"
|
||||
y1="256"
|
||||
x2="184"
|
||||
y2="272"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#5CBCB0" />
|
||||
<stop offset="1" stopColor="#FAC818" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_7238_190934"
|
||||
x1="196"
|
||||
y1="256"
|
||||
x2="196"
|
||||
y2="272"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#5CBCB0" />
|
||||
<stop offset="1" stopColor="#133BFE" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function
|
||||
export function RecommendNoResultDark(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width="576"
|
||||
height="288"
|
||||
viewBox="0 0 576 288"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
x="148.5"
|
||||
y="136.5"
|
||||
width="279"
|
||||
height="151"
|
||||
rx="7.5"
|
||||
fill="#1C2333"
|
||||
/>
|
||||
<rect
|
||||
x="148.5"
|
||||
y="136.5"
|
||||
width="279"
|
||||
height="151"
|
||||
rx="7.5"
|
||||
stroke="url(#paint0_linear_7238_218933)"
|
||||
/>
|
||||
<rect x="164" y="152" width="104" height="14" rx="7" fill="#2B3245" />
|
||||
<rect x="164" y="174" width="248" height="14" rx="7" fill="#2B3245" />
|
||||
<rect x="164" y="196" width="248" height="14" rx="7" fill="#2B3245" />
|
||||
<rect x="164" y="226" width="120" height="14" rx="7" fill="#2B3245" />
|
||||
<g filter="url(#filter0_d_7238_218933)">
|
||||
<path
|
||||
d="M164 262.4C164 260.16 164 259.04 164.436 258.184C164.819 257.431 165.431 256.819 166.184 256.436C167.04 256 168.16 256 170.4 256H173.6C175.84 256 176.96 256 177.816 256.436C178.569 256.819 179.181 257.431 179.564 258.184C180 259.04 180 260.16 180 262.4V265.6C180 267.84 180 268.96 179.564 269.816C179.181 270.569 178.569 271.181 177.816 271.564C176.96 272 175.84 272 173.6 272H170.4C168.16 272 167.04 272 166.184 271.564C165.431 271.181 164.819 270.569 164.436 269.816C164 268.96 164 267.84 164 265.6V262.4Z"
|
||||
fill="url(#paint1_linear_7238_218933)"
|
||||
/>
|
||||
<path
|
||||
d="M170.4 272.5H173.6H173.623C174.724 272.5 175.581 272.5 176.268 272.444C176.966 272.387 177.533 272.269 178.043 272.01C178.89 271.578 179.578 270.89 180.01 270.043C180.269 269.533 180.387 268.966 180.444 268.268C180.5 267.581 180.5 266.724 180.5 265.623V265.6V262.4V262.377C180.5 261.276 180.5 260.419 180.444 259.732C180.387 259.034 180.269 258.467 180.01 257.957C179.578 257.11 178.89 256.422 178.043 255.99C177.533 255.731 176.966 255.613 176.268 255.556C175.581 255.5 174.724 255.5 173.623 255.5H173.6H170.4H170.377C169.276 255.5 168.419 255.5 167.732 255.556C167.034 255.613 166.467 255.731 165.957 255.99C165.11 256.422 164.422 257.11 163.99 257.957C163.731 258.467 163.613 259.034 163.556 259.732C163.5 260.419 163.5 261.276 163.5 262.377V262.4V265.6V265.623C163.5 266.724 163.5 267.581 163.556 268.268C163.613 268.966 163.731 269.533 163.99 270.043C164.422 270.89 165.11 271.578 165.957 272.01C166.467 272.269 167.034 272.387 167.732 272.444C168.419 272.5 169.276 272.5 170.377 272.5H170.4Z"
|
||||
stroke="white"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter1_d_7238_218933)">
|
||||
<path
|
||||
d="M176 262.4C176 260.16 176 259.04 176.436 258.184C176.819 257.431 177.431 256.819 178.184 256.436C179.04 256 180.16 256 182.4 256H185.6C187.84 256 188.96 256 189.816 256.436C190.569 256.819 191.181 257.431 191.564 258.184C192 259.04 192 260.16 192 262.4V265.6C192 267.84 192 268.96 191.564 269.816C191.181 270.569 190.569 271.181 189.816 271.564C188.96 272 187.84 272 185.6 272H182.4C180.16 272 179.04 272 178.184 271.564C177.431 271.181 176.819 270.569 176.436 269.816C176 268.96 176 267.84 176 265.6V262.4Z"
|
||||
fill="url(#paint2_linear_7238_218933)"
|
||||
/>
|
||||
<path
|
||||
d="M182.4 272.5H185.6H185.623C186.724 272.5 187.581 272.5 188.268 272.444C188.966 272.387 189.533 272.269 190.043 272.01C190.89 271.578 191.578 270.89 192.01 270.043C192.269 269.533 192.387 268.966 192.444 268.268C192.5 267.581 192.5 266.724 192.5 265.623V265.6V262.4V262.377C192.5 261.276 192.5 260.419 192.444 259.732C192.387 259.034 192.269 258.467 192.01 257.957C191.578 257.11 190.89 256.422 190.043 255.99C189.533 255.731 188.966 255.613 188.268 255.556C187.581 255.5 186.724 255.5 185.623 255.5H185.6H182.4H182.377C181.276 255.5 180.419 255.5 179.732 255.556C179.034 255.613 178.467 255.731 177.957 255.99C177.11 256.422 176.422 257.11 175.99 257.957C175.731 258.467 175.613 259.034 175.556 259.732C175.5 260.419 175.5 261.276 175.5 262.377V262.4V265.6V265.623C175.5 266.724 175.5 267.581 175.556 268.268C175.613 268.966 175.731 269.533 175.99 270.043C176.422 270.89 177.11 271.578 177.957 272.01C178.467 272.269 179.034 272.387 179.732 272.444C180.419 272.5 181.276 272.5 182.377 272.5H182.4Z"
|
||||
stroke="white"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter2_d_7238_218933)">
|
||||
<path
|
||||
d="M188 262.4C188 260.16 188 259.04 188.436 258.184C188.819 257.431 189.431 256.819 190.184 256.436C191.04 256 192.16 256 194.4 256H197.6C199.84 256 200.96 256 201.816 256.436C202.569 256.819 203.181 257.431 203.564 258.184C204 259.04 204 260.16 204 262.4V265.6C204 267.84 204 268.96 203.564 269.816C203.181 270.569 202.569 271.181 201.816 271.564C200.96 272 199.84 272 197.6 272H194.4C192.16 272 191.04 272 190.184 271.564C189.431 271.181 188.819 270.569 188.436 269.816C188 268.96 188 267.84 188 265.6V262.4Z"
|
||||
fill="url(#paint3_linear_7238_218933)"
|
||||
/>
|
||||
<path
|
||||
d="M194.4 272.5H197.6H197.623C198.724 272.5 199.581 272.5 200.268 272.444C200.966 272.387 201.533 272.269 202.043 272.01C202.89 271.578 203.578 270.89 204.01 270.043C204.269 269.533 204.387 268.966 204.444 268.268C204.5 267.581 204.5 266.724 204.5 265.623V265.6V262.4V262.377C204.5 261.276 204.5 260.419 204.444 259.732C204.387 259.034 204.269 258.467 204.01 257.957C203.578 257.11 202.89 256.422 202.043 255.99C201.533 255.731 200.966 255.613 200.268 255.556C199.581 255.5 198.724 255.5 197.623 255.5H197.6H194.4H194.377C193.276 255.5 192.419 255.5 191.732 255.556C191.034 255.613 190.467 255.731 189.957 255.99C189.11 256.422 188.422 257.11 187.99 257.957C187.731 258.467 187.613 259.034 187.556 259.732C187.5 260.419 187.5 261.276 187.5 262.377V262.4V265.6V265.623C187.5 266.724 187.5 267.581 187.556 268.268C187.613 268.966 187.731 269.533 187.99 270.043C188.422 270.89 189.11 271.578 189.957 272.01C190.467 272.269 191.034 272.387 191.732 272.444C192.419 272.5 193.276 272.5 194.377 272.5H194.4Z"
|
||||
stroke="white"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_7238_218933"
|
||||
x="154.429"
|
||||
y="249.286"
|
||||
width="35.1429"
|
||||
height="35.1429"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2.85714" />
|
||||
<feGaussianBlur stdDeviation="4.28571" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_7238_218933"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_7238_218933"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_d_7238_218933"
|
||||
x="166.429"
|
||||
y="249.286"
|
||||
width="35.1429"
|
||||
height="35.1429"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2.85714" />
|
||||
<feGaussianBlur stdDeviation="4.28571" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_7238_218933"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_7238_218933"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_d_7238_218933"
|
||||
x="178.429"
|
||||
y="249.286"
|
||||
width="35.1429"
|
||||
height="35.1429"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2.85714" />
|
||||
<feGaussianBlur stdDeviation="4.28571" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.16 0"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in2="BackgroundImageFix"
|
||||
result="effect1_dropShadow_7238_218933"
|
||||
/>
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_7238_218933"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_7238_218933"
|
||||
x1="288"
|
||||
y1="136"
|
||||
x2="288"
|
||||
y2="288"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#38415A" />
|
||||
<stop offset="1" stopColor="#2B3245" stopOpacity="0.4" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_7238_218933"
|
||||
x1="172"
|
||||
y1="256"
|
||||
x2="172"
|
||||
y2="272"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#F45D68" />
|
||||
<stop offset="1" stopColor="#FFCA00" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_7238_218933"
|
||||
x1="184"
|
||||
y1="256"
|
||||
x2="184"
|
||||
y2="272"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#5CBCB0" />
|
||||
<stop offset="1" stopColor="#FAC818" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_7238_218933"
|
||||
x1="196"
|
||||
y1="256"
|
||||
x2="196"
|
||||
y2="272"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#5CBCB0" />
|
||||
<stop offset="1" stopColor="#133BFE" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 { ThemeFactory, type COZTheme } from '../../factory';
|
||||
import { RecommendNoResultDark } from './RecommendNoResultDark';
|
||||
import { RecommendNoResult } from './RecommendNoResult';
|
||||
export function RecommendSearchNoCard({ theme }: COZTheme) {
|
||||
return (
|
||||
<ThemeFactory
|
||||
theme={theme}
|
||||
components={{
|
||||
dark: <RecommendNoResultDark />,
|
||||
light: <RecommendNoResult />,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user