feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 { Modal, Form, Input, type ModalProps } from '@coze-arch/coze-design';
|
||||
|
||||
export interface DeleteProjectBaseProps {
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export interface DeleteProjectModalProps
|
||||
extends Omit<
|
||||
ModalProps,
|
||||
'size' | 'footer' | 'header' | 'okButtonColor' | 'okText' | 'cancelText'
|
||||
>,
|
||||
DeleteProjectBaseProps {}
|
||||
|
||||
const DeleteProjectContent: React.FC<DeleteProjectBaseProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
}) => (
|
||||
<>
|
||||
<div className="coz-fg-secondary leading-20px text-[14px] font-normal mb-16px">
|
||||
{I18n.t('project_ide_delete_confirm_describe')}
|
||||
</div>
|
||||
<Form.Label required>{I18n.t('project_ide_project_name')}</Form.Label>
|
||||
<Input value={value} onChange={onChange} placeholder={placeholder} />
|
||||
</>
|
||||
);
|
||||
|
||||
export const DeleteProjectModal: React.FC<DeleteProjectModalProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
...restModalProps
|
||||
}) => (
|
||||
<Modal
|
||||
size="default"
|
||||
header={I18n.t('project_ide_delete_confirm')}
|
||||
okButtonColor="red"
|
||||
okText={I18n.t('project_ide_delete')}
|
||||
cancelText={I18n.t('Cancel')}
|
||||
{...restModalProps}
|
||||
>
|
||||
<DeleteProjectContent
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
@@ -0,0 +1,37 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.guide-modal {
|
||||
:global(.semi-modal-content) {
|
||||
padding-right: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
:global(.semi-modal-header) {
|
||||
padding-right: 24px;
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
:global(.semi-modal-footer) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.guide-button {
|
||||
box-shadow: -8px -18px 30px 0 #DAE6F7 inset;
|
||||
}
|
||||
|
||||
.guide-button-hover {
|
||||
&:hover {
|
||||
.guide-img-bg {
|
||||
background: linear-gradient(29deg, rgba(79, 62, 255, 17%) 15.59%, rgba(242, 146, 255, 14%) 42.22%, rgba(242, 146, 255, 0%) 72.41%), var(--coz-bg-primary, #F8F8FC);
|
||||
}
|
||||
|
||||
.guide-desc-hover {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.create-button-hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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 ReactNode } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { IconCozCross } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Avatar,
|
||||
Modal,
|
||||
type ModalProps,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { useHiddenSession } from '../../hooks/use-hidden-session';
|
||||
import ProjectImg from '../../assets/project-img.png';
|
||||
import ProjectImgOversea from '../../assets/project-img-oversea.png';
|
||||
import AgentImg from '../../assets/agent-img.png';
|
||||
import AgentImgOversea from '../../assets/agent-img-oversea.png';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export type CreateType = 'project' | 'agent';
|
||||
|
||||
export interface GuideModalProps
|
||||
extends Omit<ModalProps, 'size' | 'footer' | 'header' | 'onCancel'> {
|
||||
onCancel: () => void;
|
||||
onChange: (type: CreateType) => void;
|
||||
extraButtonConfigs?: GuideButtonProps[];
|
||||
}
|
||||
|
||||
interface GuideButtonProps {
|
||||
onClick: () => void;
|
||||
assetSrc: string;
|
||||
title: ReactNode;
|
||||
description: ReactNode;
|
||||
tip?: ReactNode;
|
||||
}
|
||||
|
||||
export const GuideButton: React.FC<GuideButtonProps> = ({
|
||||
onClick,
|
||||
assetSrc,
|
||||
title,
|
||||
description,
|
||||
tip,
|
||||
}) => {
|
||||
const { isSessionHidden, hideSession } = useHiddenSession(
|
||||
tip ? 'guideTip' : '',
|
||||
);
|
||||
const showTip = !isSessionHidden && Boolean(tip);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className={classNames(
|
||||
'relative cursor-pointer p-8px pb-16px hover:coz-shadow-default coz-bg-max coz-stroke-primary border-[1px] border-solid rounded-[12px] flex flex-col items-center',
|
||||
styles['guide-button-hover'],
|
||||
)}
|
||||
>
|
||||
<Avatar
|
||||
src={assetSrc}
|
||||
className={classNames(
|
||||
'w-[314px] h-[240px] rounded-[8px] coz-bg-secondary',
|
||||
styles['guide-img-bg'],
|
||||
styles['guide-button'],
|
||||
{
|
||||
'!mb-[-28px]': showTip,
|
||||
},
|
||||
)}
|
||||
imgCls="w-full h-full"
|
||||
bottomSlot={{
|
||||
render: () =>
|
||||
showTip ? (
|
||||
<div className="z-10 px-2 coz-fg-hglt text-[12px] font-medium w-full flex justify-center items-center h-[28px] rounded-[4px] rounded-t-none bg-[#DEDBFF]">
|
||||
<div className="mx-auto">{tip}</div>
|
||||
<IconCozCross
|
||||
className="w-[12px] h-[12px]"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
hideSession();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null,
|
||||
text: tip,
|
||||
textColor: '',
|
||||
bgColor: '#DEDBFF',
|
||||
className: '',
|
||||
}}
|
||||
/>
|
||||
<div className="mb-[4px] mt-[20px] coz-fg-plus text-[20px] font-medium leading-[28px]">
|
||||
{title}
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
'mb-[8px] coz-fg-secondary text-[14px] font-normal leading-[20px] opacity-100',
|
||||
styles['guide-desc-hover'],
|
||||
)}
|
||||
>
|
||||
{description}
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute w-full flex justify-center left-0 bottom-[12px] opacity-0',
|
||||
styles['create-button-hover'],
|
||||
)}
|
||||
>
|
||||
<Button>{I18n.t('create_title')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ProjectAsset = IS_OVERSEA ? ProjectImgOversea : ProjectImg;
|
||||
const AgentAsset = IS_OVERSEA ? AgentImgOversea : AgentImg;
|
||||
|
||||
export const GuideModal: React.FC<GuideModalProps> = ({
|
||||
onChange,
|
||||
extraButtonConfigs = [],
|
||||
...modalProps
|
||||
}) => (
|
||||
<Modal
|
||||
// 清除 modal 自带边距 由内部 padding 撑开 展示按钮阴影
|
||||
className={styles['guide-modal']}
|
||||
size="xl"
|
||||
title={I18n.t('create_title')}
|
||||
width={'fit-content'}
|
||||
{...modalProps}
|
||||
>
|
||||
<div className="flex justify-between pl-24px pb-24px pr-24px gap-[8px]">
|
||||
<GuideButton
|
||||
onClick={() => onChange('agent')}
|
||||
assetSrc={AgentAsset}
|
||||
title={I18n.t('creat_project_creat_agent')}
|
||||
description={I18n.t('creat_project_agent_describe')}
|
||||
tip={!IS_OPEN_SOURCE ? I18n.t('agent_creat_tips') : null}
|
||||
/>
|
||||
<GuideButton
|
||||
onClick={() => onChange('project')}
|
||||
assetSrc={ProjectAsset}
|
||||
title={
|
||||
<span className="flex gap-x-4px items-center">
|
||||
{I18n.t('creat_project_creat_project')}
|
||||
<Badge count="Beta" type="alt" />
|
||||
</span>
|
||||
}
|
||||
description={
|
||||
IS_OPEN_SOURCE
|
||||
? I18n.t('creat_project_describe_open')
|
||||
: I18n.t('creat_project_describe')
|
||||
}
|
||||
/>
|
||||
{extraButtonConfigs.map(({ onClick, ...config }, index) => (
|
||||
<GuideButton
|
||||
key={index}
|
||||
onClick={() => {
|
||||
modalProps.onCancel();
|
||||
onClick();
|
||||
}}
|
||||
{...config}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
GuideModal.displayName = 'GuideModal';
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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, Suspense, lazy } from 'react';
|
||||
|
||||
import { SpaceFormSelect } from '@coze-studio/components';
|
||||
import { type AuditData } from '@coze-arch/idl/intelligence_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { type RenderAutoGenerateParams } from '@coze-common/biz-components/picture-upload';
|
||||
import { type FormApi, Modal, type ModalProps } from '@coze-arch/coze-design';
|
||||
|
||||
import {
|
||||
filedKeyMap,
|
||||
ProjectForm,
|
||||
type ProjectFormProps,
|
||||
type ProjectFormValues,
|
||||
ProjectInfoFieldFragment,
|
||||
} from '../project-form';
|
||||
import { useFormSubmitState } from '../../hooks/use-project-form-submit-state';
|
||||
|
||||
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>
|
||||
);
|
||||
interface ProjectFormModalProps
|
||||
extends Omit<
|
||||
ModalProps,
|
||||
'size' | 'okText' | 'cancelText' | 'okButtonProps' | 'onOk'
|
||||
> {
|
||||
/** @default false */
|
||||
showMonetizeConfig?: boolean;
|
||||
selectSpace?: boolean;
|
||||
formProps?: Omit<ProjectFormProps, 'getFormApi' | 'onValueChange'>;
|
||||
request: (param: ProjectFormValues) => Promise<AuditData>;
|
||||
isFormValid: (values: ProjectFormValues) => boolean;
|
||||
}
|
||||
|
||||
export type BizProjectFormModalProps = ProjectFormModalProps & {
|
||||
renderAutoGenerate?: (params: RenderAutoGenerateParams) => React.ReactNode;
|
||||
};
|
||||
|
||||
export const ProjectFormModal: React.FC<BizProjectFormModalProps> = ({
|
||||
selectSpace,
|
||||
formProps = {},
|
||||
isFormValid,
|
||||
request,
|
||||
showMonetizeConfig,
|
||||
renderAutoGenerate,
|
||||
...restModalProps
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [auditResult, setAuditResult] = useState<AuditData>({
|
||||
check_not_pass: false,
|
||||
});
|
||||
const {
|
||||
bizCallback: { onAfterUpload, onBeforeUpload, onValuesChange },
|
||||
isSubmitDisabled,
|
||||
} = useFormSubmitState<ProjectFormValues>({
|
||||
initialValues: formProps.initValues,
|
||||
getIsFormValid: isFormValid,
|
||||
});
|
||||
const formApi = useRef<FormApi<ProjectFormValues>>();
|
||||
|
||||
const onFormSubmit: ModalProps['onOk'] = async () => {
|
||||
if (!formApi.current) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setLoading(true);
|
||||
const auditData = await request(formApi.current.getValues());
|
||||
setAuditResult(auditData);
|
||||
|
||||
// 没有通过校验就不关闭弹窗
|
||||
if (auditData.check_not_pass) {
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="default"
|
||||
okText={I18n.t('Confirm')}
|
||||
cancelText={I18n.t('Cancel')}
|
||||
okButtonProps={{
|
||||
disabled: isSubmitDisabled,
|
||||
loading,
|
||||
}}
|
||||
onOk={onFormSubmit}
|
||||
{...restModalProps}
|
||||
>
|
||||
<ProjectForm
|
||||
{...formProps}
|
||||
getFormApi={api => {
|
||||
formApi.current = api;
|
||||
}}
|
||||
onValueChange={onValuesChange}
|
||||
>
|
||||
{selectSpace ? <SpaceFormSelect field={filedKeyMap.space_id} /> : null}
|
||||
<ProjectInfoFieldFragment
|
||||
showMonetizeConfig={showMonetizeConfig}
|
||||
onBeforeUpload={onBeforeUpload}
|
||||
onAfterUpload={onAfterUpload}
|
||||
renderAutoGenerate={renderAutoGenerate}
|
||||
/>
|
||||
</ProjectForm>
|
||||
{auditResult.check_not_pass ? (
|
||||
<div className="coz-fg-hglt-red mt-[-8px]">
|
||||
<ReactMarkdown skipHtml={true} linkTarget="_blank">
|
||||
{/* 注意使用 || msg undefined 或者空字符串都走兜底 */}
|
||||
{auditResult.check_not_pass_msg || I18n.t('publish_audit_pop7')}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
) : null}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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 {
|
||||
type DraftProjectCopyRequest,
|
||||
type DraftProjectUpdateRequest,
|
||||
type DraftProjectCreateRequest,
|
||||
} from '@coze-arch/idl/intelligence_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { FileBizType, IconType } from '@coze-arch/bot-api/developer_api';
|
||||
import {
|
||||
PictureUpload,
|
||||
type RenderAutoGenerateParams,
|
||||
} from '@coze-common/biz-components/picture-upload';
|
||||
import { botInputLengthService } from '@coze-agent-ide/bot-input-length-limit';
|
||||
import { IconCozUpload } from '@coze-arch/coze-design/icons';
|
||||
import {
|
||||
type BaseFormProps,
|
||||
Form,
|
||||
FormInput,
|
||||
FormTextArea,
|
||||
useFormApi,
|
||||
withField,
|
||||
} from '@coze-arch/coze-design';
|
||||
|
||||
import { SwitchWithDesc } from '../switch-with-desc';
|
||||
import { type ModifyUploadValueType } from '../../type';
|
||||
|
||||
export type ProjectFormValues = ModifyUploadValueType<
|
||||
Omit<DraftProjectCreateRequest, 'monetization_conf' | 'create_from'> &
|
||||
DraftProjectCopyRequest &
|
||||
DraftProjectUpdateRequest & {
|
||||
enableMonetize?: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
export type ProjectFormSubmitValues = DraftProjectCreateRequest;
|
||||
|
||||
export type ProjectFormProps = BaseFormProps<ProjectFormValues>;
|
||||
|
||||
export interface ProjectInfoFieldProps {
|
||||
/** @default false */
|
||||
showMonetizeConfig?: boolean;
|
||||
onBeforeUpload?: () => void;
|
||||
onAfterUpload?: () => void;
|
||||
renderAutoGenerate?: (params: RenderAutoGenerateParams) => React.ReactNode;
|
||||
}
|
||||
|
||||
export const ProjectForm: React.FC<PropsWithChildren<ProjectFormProps>> = ({
|
||||
children,
|
||||
...formProps
|
||||
}) => <Form<ProjectFormValues> {...formProps}>{children}</Form>;
|
||||
|
||||
export const filedKeyMap: Record<
|
||||
keyof ProjectFormValues,
|
||||
keyof ProjectFormValues
|
||||
> = {
|
||||
name: 'name',
|
||||
enableMonetize: 'enableMonetize',
|
||||
description: 'description',
|
||||
icon_uri: 'icon_uri',
|
||||
space_id: 'space_id',
|
||||
project_id: 'project_id',
|
||||
to_space_id: 'to_space_id',
|
||||
} as const;
|
||||
|
||||
export const ProjectInfoFieldFragment: React.FC<ProjectInfoFieldProps> = ({
|
||||
showMonetizeConfig,
|
||||
onAfterUpload,
|
||||
onBeforeUpload,
|
||||
renderAutoGenerate,
|
||||
}) => {
|
||||
const formApi = useFormApi<ProjectFormValues>();
|
||||
return (
|
||||
<>
|
||||
<FormInput
|
||||
label={I18n.t('creat_project_project_name')}
|
||||
rules={[{ required: true }]}
|
||||
field={filedKeyMap.name}
|
||||
maxLength={botInputLengthService.getInputLengthLimit('projectName')}
|
||||
getValueLength={botInputLengthService.getValueLength}
|
||||
noErrorMessage
|
||||
/>
|
||||
{showMonetizeConfig ? (
|
||||
<FormSwitch
|
||||
field={filedKeyMap.enableMonetize}
|
||||
label={I18n.t('monetization')}
|
||||
desc={I18n.t('monetization_des')}
|
||||
initValue={true}
|
||||
rules={[{ required: true }]}
|
||||
/>
|
||||
) : null}
|
||||
<FormTextArea
|
||||
label={I18n.t('creat_project_project_describe')}
|
||||
field={filedKeyMap.description}
|
||||
maxCount={botInputLengthService.getInputLengthLimit(
|
||||
'projectDescription',
|
||||
)}
|
||||
maxLength={botInputLengthService.getInputLengthLimit(
|
||||
'projectDescription',
|
||||
)}
|
||||
getValueLength={botInputLengthService.getValueLength}
|
||||
/>
|
||||
<PictureUpload
|
||||
accept=".jpeg,.jpg,.png,.gif"
|
||||
label={I18n.t('bot_edit_profile_pircture')}
|
||||
field={filedKeyMap.icon_uri}
|
||||
rules={[{ required: true }]}
|
||||
fileBizType={FileBizType.BIZ_BOT_ICON}
|
||||
iconType={IconType.Bot}
|
||||
maskIcon={<IconCozUpload />}
|
||||
withAutoGenerate
|
||||
renderAutoGenerate={renderAutoGenerate}
|
||||
generateInfo={() => {
|
||||
const values = formApi.getValues();
|
||||
return {
|
||||
name: values?.name,
|
||||
desc: values?.description,
|
||||
};
|
||||
}}
|
||||
beforeUploadCustom={onBeforeUpload}
|
||||
afterUploadCustom={onAfterUpload}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const FormSwitch = withField(SwitchWithDesc);
|
||||
@@ -0,0 +1,35 @@
|
||||
/* stylelint-disable declaration-no-important */
|
||||
.project-template-modal {
|
||||
:global(.semi-modal) {
|
||||
width: 960px !important;
|
||||
}
|
||||
|
||||
|
||||
:global(.semi-modal-content) {
|
||||
padding-right: 0 !important;
|
||||
padding-bottom: 0 !important;
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
|
||||
:global(.semi-modal-header) {
|
||||
padding-right: 24px;
|
||||
padding-left: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.error-empty {
|
||||
padding: 124px 0;
|
||||
|
||||
:global {
|
||||
.semi-empty-content {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.semi-empty-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* 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 { groupBy, xorBy } from 'lodash-es';
|
||||
import { useRequest } from 'ahooks';
|
||||
import {
|
||||
ProductEntityType,
|
||||
ProductListSource,
|
||||
SortType,
|
||||
type ProductInfo,
|
||||
} from '@coze-arch/idl/product_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { ProductApi } from '@coze-arch/bot-api';
|
||||
import {
|
||||
IllustrationFailure,
|
||||
IllustrationFailureDark,
|
||||
} from '@douyinfe/semi-illustrations';
|
||||
import { IconCozRefresh } from '@coze-arch/coze-design/icons';
|
||||
import { Button, Empty, Modal, type ModalProps } from '@coze-arch/coze-design';
|
||||
|
||||
import {
|
||||
type BeforeProjectTemplateCopyCallback,
|
||||
type ProjectTemplateCopySuccessCallback,
|
||||
useProjectTemplateCopyModal,
|
||||
} from '../../hooks/use-project-template-copy-modal';
|
||||
import {
|
||||
CardSkeleton,
|
||||
TemplateGroupSkeleton,
|
||||
} from './template-components/skeleton';
|
||||
import { ProjectTemplateGroup } from './template-components/project-template-group';
|
||||
import {
|
||||
openTemplatePreview,
|
||||
ProjectTemplateCard,
|
||||
} from './template-components/project-template-card';
|
||||
import { CreateEmptyProjectUI } from './template-components/create-empty-project-ui';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
const MAX_PAGE_SIZE = 50;
|
||||
|
||||
export interface ProjectTemplateBaseProps {
|
||||
spaceId?: string;
|
||||
isSelectSpaceOnCopy: boolean;
|
||||
onBeforeCopy: BeforeProjectTemplateCopyCallback | undefined;
|
||||
onCopyError: (() => void) | undefined;
|
||||
onCopyOk: ProjectTemplateCopySuccessCallback | undefined;
|
||||
onCreateProject?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 需要特别处理、放到「基础」类别中的模版
|
||||
* 本身业务中没有「基础」这个类别,但是在这个通过复制创建的场景下 pm 希望为用户提供一些具有基本功能有代表性的模版
|
||||
* 所以这个特殊处理的模版应运而生
|
||||
*
|
||||
* 迭代时需要注意,业务上需要保证这些模版都是被 recommend 才能复用 PublicGetProductList 这个接口
|
||||
*/
|
||||
const BASE_TEMPLATE_ID_LIST = ['7439261984903938074'];
|
||||
|
||||
const ProjectTemplateContent: React.FC<ProjectTemplateBaseProps> = ({
|
||||
spaceId,
|
||||
isSelectSpaceOnCopy,
|
||||
onCopyOk,
|
||||
onCreateProject,
|
||||
onBeforeCopy,
|
||||
onCopyError,
|
||||
}) => {
|
||||
const {
|
||||
data: categories,
|
||||
error: categoriesError,
|
||||
loading: isCategoryLoading,
|
||||
refresh: refreshCategoryRequest,
|
||||
} = useRequest(async () => {
|
||||
const response = await ProductApi.PublicGetProductCategoryList({
|
||||
entity_type: ProductEntityType.TemplateCommon,
|
||||
});
|
||||
return response.data?.categories;
|
||||
});
|
||||
|
||||
const {
|
||||
data: products,
|
||||
error: productsError,
|
||||
loading: isProductLoading,
|
||||
refresh: refreshProductRequest,
|
||||
} = useRequest(async () => {
|
||||
const response = await ProductApi.PublicGetProductList({
|
||||
entity_type: ProductEntityType.ProjectTemplate,
|
||||
page_num: 1,
|
||||
page_size: MAX_PAGE_SIZE,
|
||||
sort_type: SortType.Heat,
|
||||
source: ProductListSource.Recommend,
|
||||
is_free: true,
|
||||
});
|
||||
return response.data?.products;
|
||||
});
|
||||
|
||||
const { copyProject, modalContextHolder } = useProjectTemplateCopyModal({
|
||||
onSuccess: onCopyOk,
|
||||
source: spaceId ? 'space' : 'navi',
|
||||
onBefore: onBeforeCopy,
|
||||
onError: onCopyError,
|
||||
});
|
||||
const refreshRequest = () => {
|
||||
refreshCategoryRequest();
|
||||
refreshProductRequest();
|
||||
};
|
||||
const isRequestLoading = isCategoryLoading || isProductLoading;
|
||||
const isRequestError = Boolean(categoriesError || productsError);
|
||||
|
||||
if (!categories || !products) {
|
||||
return (
|
||||
<>
|
||||
{modalContextHolder}
|
||||
<div className="px-24px flex flex-col gap-y-[20px]">
|
||||
<ProjectTemplateGroup title="基础">
|
||||
<CreateEmptyProjectUI onClick={onCreateProject} />
|
||||
{isRequestLoading ? (
|
||||
<>
|
||||
<CardSkeleton />
|
||||
<CardSkeleton />
|
||||
</>
|
||||
) : null}
|
||||
</ProjectTemplateGroup>
|
||||
{isRequestLoading ? (
|
||||
<>
|
||||
<TemplateGroupSkeleton />
|
||||
<TemplateGroupSkeleton />
|
||||
</>
|
||||
) : null}
|
||||
{!isRequestLoading && isRequestError ? (
|
||||
<Empty
|
||||
className={styles['error-empty']}
|
||||
image={<IllustrationFailure className="h-160px w-160px" />}
|
||||
darkModeImage={
|
||||
<IllustrationFailureDark className="h-160px w-160px" />
|
||||
}
|
||||
title={
|
||||
<span className="coz-fg-primary text-[14px] font-medium leading-20px">
|
||||
{I18n.t('creat_project_templates_load_failed')}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Button onClick={refreshRequest} icon={<IconCozRefresh />}>
|
||||
{I18n.t('Retry')}
|
||||
</Button>
|
||||
</Empty>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const baseTemplateList = products.filter(p =>
|
||||
BASE_TEMPLATE_ID_LIST.some(id => id === p.meta_info.id),
|
||||
);
|
||||
const recommendTemplateList = xorBy(
|
||||
products,
|
||||
baseTemplateList,
|
||||
p => p.meta_info.id,
|
||||
);
|
||||
const productGroupList = groupBy(
|
||||
recommendTemplateList,
|
||||
p => p.meta_info.category?.id,
|
||||
);
|
||||
|
||||
const renderTemplateList = (productList: ProductInfo[]) =>
|
||||
productList.map(product => (
|
||||
<ProjectTemplateCard
|
||||
viewSource={spaceId ? 'space' : 'navi'}
|
||||
onClick={() => {
|
||||
openTemplatePreview(product.meta_info.id ?? '');
|
||||
}}
|
||||
onCopyTemplate={param => {
|
||||
copyProject({
|
||||
spaceId,
|
||||
isSelectSpace: isSelectSpaceOnCopy,
|
||||
productId: param.id,
|
||||
name: param.name,
|
||||
sourceProduct: product,
|
||||
});
|
||||
}}
|
||||
key={product.meta_info.id}
|
||||
product={product}
|
||||
/>
|
||||
));
|
||||
return (
|
||||
<>
|
||||
{modalContextHolder}
|
||||
<div className="px-24px flex flex-col gap-y-[20px]">
|
||||
<ProjectTemplateGroup title="基础">
|
||||
<CreateEmptyProjectUI onClick={onCreateProject} />
|
||||
{renderTemplateList(baseTemplateList)}
|
||||
</ProjectTemplateGroup>
|
||||
{categories.map(category => {
|
||||
const productList = productGroupList[category.id ?? ''];
|
||||
if (!productList?.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ProjectTemplateGroup key={category.id} title={category.name}>
|
||||
{renderTemplateList(productGroupList[category.id ?? ''] ?? [])}
|
||||
</ProjectTemplateGroup>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ProjectTemplateModal: React.FC<
|
||||
Omit<ModalProps, 'size' | 'title' | 'className' | 'footer'> &
|
||||
ProjectTemplateBaseProps
|
||||
> = ({
|
||||
spaceId,
|
||||
isSelectSpaceOnCopy,
|
||||
onCopyOk,
|
||||
onCreateProject,
|
||||
onBeforeCopy,
|
||||
onCopyError,
|
||||
...props
|
||||
}) => (
|
||||
<Modal
|
||||
size="xxl"
|
||||
title={I18n.t('creat_project_templates')}
|
||||
className={styles['project-template-modal']}
|
||||
footer={null}
|
||||
{...props}
|
||||
>
|
||||
<ProjectTemplateContent
|
||||
spaceId={spaceId}
|
||||
isSelectSpaceOnCopy={isSelectSpaceOnCopy}
|
||||
onCopyOk={onCopyOk}
|
||||
onCreateProject={onCreateProject}
|
||||
onBeforeCopy={onBeforeCopy}
|
||||
onCopyError={onCopyError}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
@@ -0,0 +1,12 @@
|
||||
.template-card-mask {
|
||||
--template-card-mask-start: rgba(var(--coze-bg-3), 0);
|
||||
--template-card-mask-end: rgba(var(--coze-bg-3), 0.96);
|
||||
|
||||
background: linear-gradient(180deg, var(--template-card-mask-start) 0%, var(--template-card-mask-end) 100%);
|
||||
}
|
||||
|
||||
.template-group {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px 1%;
|
||||
// 根据 gpt 设置 1% 会根据父容器宽度计算
|
||||
}
|
||||
@@ -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 { IconCozPlusFill } from '@coze-arch/coze-design/icons';
|
||||
|
||||
import { ProjectTemplateCardUI } from './project-template-card';
|
||||
|
||||
export const CreateEmptyProjectUI: React.FC<{
|
||||
onClick: (() => void) | undefined;
|
||||
}> = ({ onClick }) => (
|
||||
<ProjectTemplateCardUI
|
||||
onClick={onClick}
|
||||
className="h-200px flex items-center justify-center flex-col coz-fg-primary"
|
||||
>
|
||||
<IconCozPlusFill />
|
||||
<div className="py-6px px-8px text-[14px] leading-[20px] font-medium">
|
||||
{I18n.t('creat_project_creat_new_project')}
|
||||
</div>
|
||||
</ProjectTemplateCardUI>
|
||||
);
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import {
|
||||
forwardRef,
|
||||
type MouseEventHandler,
|
||||
type PropsWithChildren,
|
||||
useRef,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useHover } from 'ahooks';
|
||||
import { TeaExposure } from '@coze-studio/components';
|
||||
import { type ProductInfo } from '@coze-arch/idl/product_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { openNewWindow } from '@coze-arch/bot-utils';
|
||||
import { extractTemplateActionCommonParams } from '@coze-arch/bot-tea/utils';
|
||||
import {
|
||||
EVENT_NAMES,
|
||||
type ParamsTypeDefine,
|
||||
sendTeaEvent,
|
||||
} from '@coze-arch/bot-tea';
|
||||
import { Button, Image } from '@coze-arch/coze-design';
|
||||
|
||||
import styles from './card.module.less';
|
||||
|
||||
export interface ProjectTemplateCardContentProps {
|
||||
/** 埋点参数 页面来源 */
|
||||
viewSource: ParamsTypeDefine[EVENT_NAMES.template_action_front]['source'];
|
||||
product: ProductInfo;
|
||||
onCopyTemplate?: (param: { name: string; id: string }) => void;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const openTemplatePreview = (templateId: string) => {
|
||||
const url = new URL(
|
||||
`/template/project/${templateId}`,
|
||||
window.location.origin,
|
||||
);
|
||||
openNewWindow(() => url.toString());
|
||||
};
|
||||
|
||||
const ActionButton: React.FC<ProjectTemplateCardContentProps> = ({
|
||||
viewSource,
|
||||
product,
|
||||
className,
|
||||
onCopyTemplate,
|
||||
}) => {
|
||||
const onPreview: MouseEventHandler<HTMLButtonElement> = e => {
|
||||
e.stopPropagation();
|
||||
sendTeaEvent(EVENT_NAMES.template_action_front, {
|
||||
action: 'click',
|
||||
source: viewSource,
|
||||
...extractTemplateActionCommonParams(product),
|
||||
});
|
||||
openTemplatePreview(product.meta_info.id ?? '');
|
||||
};
|
||||
const onCopy: MouseEventHandler<HTMLButtonElement> = e => {
|
||||
e.stopPropagation();
|
||||
onCopyTemplate?.({
|
||||
name: product.meta_info.name ?? '',
|
||||
id: product.meta_info.id ?? '',
|
||||
});
|
||||
};
|
||||
|
||||
const isShowCopyActionButton = !product.meta_info.is_professional;
|
||||
|
||||
return (
|
||||
<div className={classNames('w-full px-12px', className)}>
|
||||
<div
|
||||
className={classNames('w-full h-24px', styles['template-card-mask'])}
|
||||
/>
|
||||
<div className="w-full flex justify-between pt-8px coz-bg-max gap-x-8px">
|
||||
<Button color="highlight" className="flex-[1]" onClick={onPreview}>
|
||||
{I18n.t('creat_project_use_template_preview')}
|
||||
</Button>
|
||||
{isShowCopyActionButton ? (
|
||||
<Button color="hgltplus" className="flex-[1]" onClick={onCopy}>
|
||||
{I18n.t('creat_project_use_template_use')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ProjectTemplateCardUI = forwardRef<
|
||||
HTMLDivElement,
|
||||
PropsWithChildren<{ className?: string; onClick?: () => void }>
|
||||
>(({ className, children, onClick }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
onClick={onClick}
|
||||
className={classNames(
|
||||
'cursor-pointer p-12px coz-bg-max coz-stroke-primary border-solid border-[1px] hover:coz-shadow-default rounded-[16px]',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
));
|
||||
|
||||
export const ProjectTemplateCard: React.FC<ProjectTemplateCardContentProps> = ({
|
||||
viewSource,
|
||||
product,
|
||||
onCopyTemplate,
|
||||
className,
|
||||
onClick,
|
||||
}) => {
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
const isHover = useHover(divRef);
|
||||
return (
|
||||
<ProjectTemplateCardUI
|
||||
ref={divRef}
|
||||
className={classNames('relative', className)}
|
||||
onClick={() => {
|
||||
sendTeaEvent(EVENT_NAMES.template_action_front, {
|
||||
action: 'click',
|
||||
source: viewSource,
|
||||
...extractTemplateActionCommonParams(product),
|
||||
});
|
||||
onClick?.();
|
||||
}}
|
||||
>
|
||||
<TeaExposure
|
||||
once
|
||||
teaEvent={{
|
||||
name: EVENT_NAMES.template_action_front,
|
||||
params: {
|
||||
...extractTemplateActionCommonParams(product),
|
||||
action: 'card_view',
|
||||
source: viewSource,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className="px-4px mb-8px overflow-hidden text-ellipsis coz-fg-primary text-[14px] font-medium leading-[20px]">
|
||||
{product.meta_info.name}
|
||||
</div>
|
||||
<Image
|
||||
preview={false}
|
||||
src={product.meta_info.covers?.at(0)?.url}
|
||||
className="rounded-[16px] block w-full"
|
||||
imgCls="object-cover object-center w-full"
|
||||
height={148}
|
||||
/>
|
||||
<ActionButton
|
||||
viewSource={viewSource}
|
||||
product={product}
|
||||
onCopyTemplate={onCopyTemplate}
|
||||
className={classNames(
|
||||
'absolute left-0 bottom-[8px]',
|
||||
!isHover && 'hidden',
|
||||
)}
|
||||
/>
|
||||
</TeaExposure>
|
||||
</ProjectTemplateCardUI>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 ReactNode, type PropsWithChildren } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import styles from './card.module.less';
|
||||
|
||||
export interface ProjectTemplateGroupProps {
|
||||
title: ReactNode | undefined;
|
||||
groupChildrenClassName?: string;
|
||||
}
|
||||
|
||||
export const ProjectTemplateGroup: React.FC<
|
||||
PropsWithChildren<ProjectTemplateGroupProps>
|
||||
> = ({ title, groupChildrenClassName, children }) => (
|
||||
<div>
|
||||
<div className="mb-8px coz-fg-plus text-[16px] font-medium leading-[22px]">
|
||||
{title}
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
'grid',
|
||||
styles['template-group'],
|
||||
groupChildrenClassName,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -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 { Skeleton } from '@coze-arch/coze-design';
|
||||
|
||||
import { ProjectTemplateGroup } from './project-template-group';
|
||||
|
||||
export const CardSkeleton: React.FC = () => (
|
||||
<Skeleton.Image className="rounded-xl" />
|
||||
);
|
||||
|
||||
export const TemplateGroupSkeleton: React.FC = () => (
|
||||
<ProjectTemplateGroup
|
||||
title={<Skeleton.Title className="w-120px" />}
|
||||
groupChildrenClassName="h-[200px]"
|
||||
>
|
||||
<CardSkeleton />
|
||||
<CardSkeleton />
|
||||
<CardSkeleton />
|
||||
</ProjectTemplateGroup>
|
||||
);
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 { Switch, type SwitchProps } from '@coze-arch/coze-design';
|
||||
|
||||
export function SwitchWithDesc({
|
||||
value,
|
||||
onChange,
|
||||
className,
|
||||
desc,
|
||||
descClassName,
|
||||
switchClassName,
|
||||
...rest
|
||||
}: Omit<SwitchProps, 'checked'> & {
|
||||
value?: boolean;
|
||||
desc: string;
|
||||
descClassName?: string;
|
||||
switchClassName?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={cls('flex items-center justify-between', className)}>
|
||||
<span className={cls('coz-fg-primary', descClassName)}>{desc}</span>
|
||||
<Switch
|
||||
size="small"
|
||||
{...rest}
|
||||
checked={value}
|
||||
onChange={onChange}
|
||||
className={cls('shrink-0', switchClassName)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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 { useRequest } from 'ahooks';
|
||||
import { appendCopySuffix } from '@coze-studio/components';
|
||||
import {
|
||||
type IntelligenceBasicInfo,
|
||||
type User,
|
||||
} from '@coze-arch/idl/intelligence_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { intelligenceApi } from '@coze-arch/bot-api';
|
||||
import { type RenderAutoGenerateParams } from '@coze-common/biz-components/picture-upload';
|
||||
import { botInputLengthService } from '@coze-agent-ide/bot-input-length-limit';
|
||||
|
||||
import { commonProjectFormValid } from '../utils/common-project-form-valid';
|
||||
import { ProjectFormModal } from '../components/project-form-modal';
|
||||
import { type ProjectFormValues } from '../components/project-form';
|
||||
|
||||
export interface CopyProjectSuccessCallbackParam {
|
||||
basicInfo: IntelligenceBasicInfo;
|
||||
templateId: string;
|
||||
ownerInfo?: User;
|
||||
}
|
||||
|
||||
export interface UpdateProjectSuccessCallbackParam {
|
||||
projectId: string;
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
type UseBaseUpdateOrCopyProjectModalProps = {
|
||||
renderAutoGenerate?: (params: RenderAutoGenerateParams) => React.ReactNode;
|
||||
} & (
|
||||
| {
|
||||
scene: 'update';
|
||||
onSuccess?: (params: UpdateProjectSuccessCallbackParam) => void;
|
||||
}
|
||||
| {
|
||||
scene: 'copy';
|
||||
onSuccess?: (param: CopyProjectSuccessCallbackParam) => void;
|
||||
}
|
||||
);
|
||||
|
||||
export const useBaseUpdateOrCopyProjectModal = ({
|
||||
scene,
|
||||
onSuccess: inputOnSuccess,
|
||||
renderAutoGenerate,
|
||||
}: UseBaseUpdateOrCopyProjectModalProps) => {
|
||||
const [projectModalVisible, setProjectModalVisible] = useState(false);
|
||||
const [initialValues, setInitialValues] = useState<ProjectFormValues>();
|
||||
|
||||
const onModalClose = () => {
|
||||
setInitialValues(undefined);
|
||||
setProjectModalVisible(false);
|
||||
};
|
||||
|
||||
const onUpdateOk = (param: UpdateProjectSuccessCallbackParam) => {
|
||||
onModalClose();
|
||||
if (scene !== 'update') {
|
||||
throw new Error('update project error scene');
|
||||
}
|
||||
inputOnSuccess?.(param);
|
||||
};
|
||||
|
||||
const onCopyOK = (param: CopyProjectSuccessCallbackParam) => {
|
||||
onModalClose();
|
||||
if (scene !== 'copy') {
|
||||
throw new Error('copy project error scene');
|
||||
}
|
||||
inputOnSuccess?.(param);
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
onModalClose();
|
||||
};
|
||||
|
||||
const sharedProps = {
|
||||
formProps: {
|
||||
initValues: initialValues,
|
||||
},
|
||||
visible: projectModalVisible,
|
||||
onCancel,
|
||||
maskClosable: false,
|
||||
};
|
||||
|
||||
const { runAsync: updateProjectRequest } = useRequest(
|
||||
async (param: ProjectFormValues) => {
|
||||
const { icon_uri: uriList, description = '', ...restValues } = param;
|
||||
const requestFormValues = {
|
||||
...restValues,
|
||||
icon_uri: uriList?.at(0)?.uid,
|
||||
description,
|
||||
};
|
||||
const response =
|
||||
await intelligenceApi.DraftProjectUpdate(requestFormValues);
|
||||
const { audit_data } = response.data ?? {};
|
||||
return {
|
||||
...audit_data,
|
||||
};
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: (data, [inputParam]) => {
|
||||
if (data.check_not_pass) {
|
||||
return;
|
||||
}
|
||||
onUpdateOk({
|
||||
projectId: inputParam.project_id,
|
||||
spaceId: inputParam.space_id ?? '',
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const { runAsync: copyProjectRequest } = useRequest(
|
||||
async (param: ProjectFormValues) => {
|
||||
const { icon_uri: uriList, ...restValues } = param;
|
||||
const requestFormValues = {
|
||||
...restValues,
|
||||
icon_uri: uriList?.at(0)?.uid,
|
||||
};
|
||||
const response =
|
||||
await intelligenceApi.DraftProjectCopy(requestFormValues);
|
||||
const { audit_data, basic_info, user_info } = response.data ?? {};
|
||||
return {
|
||||
...audit_data,
|
||||
basic_info,
|
||||
user_info,
|
||||
};
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: (data, [inputParam]) => {
|
||||
if (!data.basic_info) {
|
||||
return;
|
||||
}
|
||||
if (data.check_not_pass) {
|
||||
return;
|
||||
}
|
||||
onCopyOK({
|
||||
templateId: inputParam.project_id,
|
||||
basicInfo: data.basic_info,
|
||||
ownerInfo: data.user_info,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const getModalTitle = () => {
|
||||
if (scene === 'copy') {
|
||||
return I18n.t('project_ide_create_duplicate');
|
||||
}
|
||||
if (scene === 'update') {
|
||||
return I18n.t('project_ide_edit_project');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
modalContextHolder: projectModalVisible ? (
|
||||
<ProjectFormModal
|
||||
{...sharedProps}
|
||||
isFormValid={commonProjectFormValid}
|
||||
title={getModalTitle()}
|
||||
request={scene === 'update' ? updateProjectRequest : copyProjectRequest}
|
||||
renderAutoGenerate={renderAutoGenerate}
|
||||
/>
|
||||
) : null,
|
||||
openModal: ({ initialValue }: { initialValue: ProjectFormValues }) => {
|
||||
setProjectModalVisible(true);
|
||||
if (scene === 'update') {
|
||||
setInitialValues(initialValue);
|
||||
return;
|
||||
}
|
||||
if (scene === 'copy') {
|
||||
setInitialValues({
|
||||
...initialValue,
|
||||
name: botInputLengthService.sliceStringByMaxLength({
|
||||
value: appendCopySuffix(initialValue.name ?? ''),
|
||||
field: 'projectName',
|
||||
}),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 ReactNode } from 'react';
|
||||
|
||||
import { type DraftProjectCopyRequest } from '@coze-arch/idl/intelligence_api';
|
||||
import { type RenderAutoGenerateParams } from '@coze-common/biz-components/picture-upload';
|
||||
|
||||
import {
|
||||
type ModifyUploadValueType,
|
||||
type RequireCopyProjectRequest,
|
||||
} from '../type';
|
||||
import {
|
||||
type CopyProjectSuccessCallbackParam,
|
||||
useBaseUpdateOrCopyProjectModal,
|
||||
} from './use-base-update-or-copy-project-modal';
|
||||
|
||||
export const useCopyProjectModalBase = ({
|
||||
onSuccess,
|
||||
renderAutoGenerate,
|
||||
}: {
|
||||
onSuccess?: (param: CopyProjectSuccessCallbackParam) => void;
|
||||
renderAutoGenerate?: (params: RenderAutoGenerateParams) => React.ReactNode;
|
||||
}): {
|
||||
modalContextHolder: ReactNode;
|
||||
openModal: (param: {
|
||||
initialValue: ModifyUploadValueType<
|
||||
RequireCopyProjectRequest<DraftProjectCopyRequest>
|
||||
>;
|
||||
}) => void;
|
||||
} =>
|
||||
useBaseUpdateOrCopyProjectModal({
|
||||
scene: 'copy',
|
||||
onSuccess,
|
||||
renderAutoGenerate,
|
||||
});
|
||||
@@ -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 { useState } from 'react';
|
||||
|
||||
import { useRequest } from 'ahooks';
|
||||
import { useCreateAgent } from '@coze-studio/entity-adapter';
|
||||
import { type RenderAutoGenerateParams } from '@coze-common/biz-components/picture-upload';
|
||||
import { type DraftProjectCreateRequest } from '@coze-arch/idl/intelligence_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { intelligenceApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { commonProjectFormValid } from '../utils/common-project-form-valid';
|
||||
import { ProjectTemplateModal } from '../components/project-template-modal';
|
||||
import {
|
||||
type BizProjectFormModalProps,
|
||||
ProjectFormModal,
|
||||
} from '../components/project-form-modal';
|
||||
import { type ProjectFormValues } from '../components/project-form';
|
||||
import {
|
||||
type CreateType,
|
||||
GuideModal,
|
||||
type GuideModalProps,
|
||||
} from '../components/guide-modal';
|
||||
import {
|
||||
type BeforeProjectTemplateCopyCallback,
|
||||
type ProjectTemplateCopySuccessCallback,
|
||||
} from './use-project-template-copy-modal';
|
||||
|
||||
type CreateBotParam = Parameters<typeof useCreateAgent>[0];
|
||||
export interface CreateProjectSuccessCallbackParam {
|
||||
projectId: string;
|
||||
spaceId: string;
|
||||
}
|
||||
export interface CreateProjectHookProps
|
||||
extends Pick<BizProjectFormModalProps, 'selectSpace'> {
|
||||
onBeforeCreateBot?: CreateBotParam['onBefore'];
|
||||
onCreateBotSuccess?: CreateBotParam['onSuccess'];
|
||||
onCreateBotError?: CreateBotParam['onError'];
|
||||
initialSpaceId?: string;
|
||||
onBeforeCreateProject?: () => void;
|
||||
onCreateProjectError?: () => void;
|
||||
onCreateProjectSuccess?: (param: CreateProjectSuccessCallbackParam) => void;
|
||||
onCopyProjectTemplateSuccess?: ProjectTemplateCopySuccessCallback;
|
||||
onBeforeCopyProjectTemplate?: BeforeProjectTemplateCopyCallback;
|
||||
onProjectTemplateCopyError?: () => void;
|
||||
/**
|
||||
* navi 导航栏
|
||||
* space workspace 右上角的按钮
|
||||
* */
|
||||
bizCreateFrom: 'navi' | 'space';
|
||||
renderAutoGenerate?: (params: RenderAutoGenerateParams) => React.ReactNode;
|
||||
extraGuideButtonConfigs?: GuideModalProps['extraButtonConfigs'];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @coze-arch/max-line-per-function
|
||||
export const useCreateProjectModalBase = ({
|
||||
selectSpace,
|
||||
onBeforeCreateBot,
|
||||
onCreateBotError,
|
||||
onCreateBotSuccess,
|
||||
initialSpaceId,
|
||||
onCreateProjectSuccess,
|
||||
onCopyProjectTemplateSuccess,
|
||||
onBeforeCreateProject,
|
||||
onCreateProjectError,
|
||||
onBeforeCopyProjectTemplate,
|
||||
onProjectTemplateCopyError,
|
||||
bizCreateFrom,
|
||||
renderAutoGenerate,
|
||||
extraGuideButtonConfigs,
|
||||
}: CreateProjectHookProps) => {
|
||||
const [guideModalVisible, setGuideModalVisible] = useState(false);
|
||||
const [projectModalVisible, setProjectModalVisible] = useState(false);
|
||||
const [projectTemplateModalVisible, setProjectTemplateModalVisible] =
|
||||
useState(false);
|
||||
const { modal, startEdit } = useCreateAgent({
|
||||
showSpace: selectSpace,
|
||||
onBefore: onBeforeCreateBot,
|
||||
onError: onCreateBotError,
|
||||
onSuccess: onCreateBotSuccess,
|
||||
spaceId: initialSpaceId,
|
||||
bizCreateFrom,
|
||||
});
|
||||
|
||||
const onGuideChange = (guideType: CreateType) => {
|
||||
setGuideModalVisible(false);
|
||||
|
||||
if (guideType === 'project') {
|
||||
// 海外版和开源版不支持项目模板
|
||||
if (IS_OVERSEA || IS_OPEN_SOURCE) {
|
||||
setProjectModalVisible(true);
|
||||
return;
|
||||
}
|
||||
setProjectTemplateModalVisible(true);
|
||||
return;
|
||||
}
|
||||
if (guideType === 'agent') {
|
||||
startEdit();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const onCreateEmptyProject = () => {
|
||||
setProjectModalVisible(true);
|
||||
setProjectTemplateModalVisible(false);
|
||||
};
|
||||
|
||||
const onGuideCancel = () => {
|
||||
setGuideModalVisible(false);
|
||||
};
|
||||
|
||||
const projectTemplateCancel = () => {
|
||||
setProjectTemplateModalVisible(false);
|
||||
};
|
||||
|
||||
const onCopyProjectTemplateOk: ProjectTemplateCopySuccessCallback =
|
||||
params => {
|
||||
setProjectTemplateModalVisible(false);
|
||||
onCopyProjectTemplateSuccess?.(params);
|
||||
};
|
||||
|
||||
const onCreateProjectOk = (param: CreateProjectSuccessCallbackParam) => {
|
||||
setProjectModalVisible(false);
|
||||
onCreateProjectSuccess?.(param);
|
||||
};
|
||||
|
||||
const onCreateProjectCancel = () => {
|
||||
setProjectModalVisible(false);
|
||||
};
|
||||
|
||||
const { runAsync: createProjectRequest } = useRequest(
|
||||
async (param: ProjectFormValues) => {
|
||||
const { icon_uri: uriList, enableMonetize, ...restValues } = param;
|
||||
const requestFormValues: DraftProjectCreateRequest = {
|
||||
...restValues,
|
||||
icon_uri: uriList?.at(0)?.uid,
|
||||
...(IS_OVERSEA && {
|
||||
monetization_conf: {
|
||||
is_enable: enableMonetize ?? true,
|
||||
},
|
||||
}),
|
||||
create_from: bizCreateFrom,
|
||||
};
|
||||
const response =
|
||||
await intelligenceApi.DraftProjectCreate(requestFormValues);
|
||||
const { project_id, audit_data } = response.data ?? {};
|
||||
return {
|
||||
...audit_data,
|
||||
project_id: project_id ?? '',
|
||||
};
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onBefore: onBeforeCreateProject,
|
||||
onError: onCreateProjectError,
|
||||
onSuccess: (data, [inputParam]) => {
|
||||
if (data.check_not_pass) {
|
||||
return;
|
||||
}
|
||||
onCreateProjectOk({
|
||||
projectId: data.project_id,
|
||||
spaceId: inputParam.space_id ?? '',
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
modalContextHolder: (
|
||||
<>
|
||||
{modal}
|
||||
<ProjectTemplateModal
|
||||
maskClosable={false}
|
||||
onCreateProject={onCreateEmptyProject}
|
||||
onBeforeCopy={onBeforeCopyProjectTemplate}
|
||||
onCopyError={onProjectTemplateCopyError}
|
||||
onCopyOk={onCopyProjectTemplateOk}
|
||||
isSelectSpaceOnCopy={Boolean(selectSpace)}
|
||||
spaceId={initialSpaceId}
|
||||
visible={projectTemplateModalVisible}
|
||||
onCancel={projectTemplateCancel}
|
||||
/>
|
||||
{guideModalVisible ? (
|
||||
<GuideModal
|
||||
visible={guideModalVisible}
|
||||
onChange={onGuideChange}
|
||||
onCancel={onGuideCancel}
|
||||
extraButtonConfigs={extraGuideButtonConfigs}
|
||||
/>
|
||||
) : null}
|
||||
{projectModalVisible ? (
|
||||
<ProjectFormModal
|
||||
showMonetizeConfig={IS_OVERSEA}
|
||||
isFormValid={values =>
|
||||
commonProjectFormValid(values) && Boolean(values.space_id)
|
||||
}
|
||||
maskClosable={false}
|
||||
title={I18n.t('creat_project_title')}
|
||||
formProps={{
|
||||
initValues: {
|
||||
space_id: initialSpaceId,
|
||||
project_id: '',
|
||||
},
|
||||
}}
|
||||
request={createProjectRequest}
|
||||
selectSpace={selectSpace}
|
||||
visible={projectModalVisible}
|
||||
onCancel={onCreateProjectCancel}
|
||||
renderAutoGenerate={renderAutoGenerate}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
),
|
||||
createProject: () => {
|
||||
setGuideModalVisible(true);
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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 { isObject } from 'lodash-es';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { DeveloperApi, intelligenceApi } from '@coze-arch/bot-api';
|
||||
|
||||
import { DeleteProjectModal } from '../components/delete-project-modal';
|
||||
|
||||
interface DeleteAgentParam {
|
||||
spaceId: string;
|
||||
agentId: string;
|
||||
}
|
||||
|
||||
interface DeleteProjectParam {
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
const isAgentParam = (value: unknown): value is DeleteAgentParam =>
|
||||
isObject(value) && 'agentId' in value;
|
||||
|
||||
const isProjectParam = (value: unknown): value is DeleteProjectParam =>
|
||||
isObject(value) && 'projectId' in value;
|
||||
|
||||
export type DeleteIntelligenceParam = (
|
||||
| DeleteAgentParam
|
||||
| DeleteProjectParam
|
||||
) & { name: string };
|
||||
|
||||
export const useDeleteIntelligence = (props?: {
|
||||
onDeleteProjectSuccess?: (param: DeleteProjectParam) => void;
|
||||
onDeleteAgentSuccess?: (param: DeleteAgentParam) => void;
|
||||
}) => {
|
||||
const [deleteIntelligenceName, setDeleteIntelligenceName] =
|
||||
useState<string>('');
|
||||
|
||||
const deleteParamsRef = useRef<DeleteProjectParam | DeleteAgentParam>();
|
||||
|
||||
const [visible, setVisible] = useState<boolean>(false);
|
||||
|
||||
const [name, setName] = useState<string>();
|
||||
|
||||
const onCloseModal = () => {
|
||||
setVisible(false);
|
||||
setDeleteIntelligenceName('');
|
||||
setName('');
|
||||
deleteParamsRef.current = undefined;
|
||||
};
|
||||
|
||||
const onDeleteCancel = () => {
|
||||
onCloseModal();
|
||||
};
|
||||
|
||||
const { loading, runAsync } = useRequest(
|
||||
async (request: DeleteAgentParam | DeleteProjectParam) => {
|
||||
if (isAgentParam(request)) {
|
||||
const { spaceId, agentId } = request;
|
||||
await DeveloperApi.DeleteDraftBot({
|
||||
space_id: spaceId,
|
||||
bot_id: agentId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isProjectParam(request)) {
|
||||
const { projectId } = request;
|
||||
await intelligenceApi.DraftProjectDelete({ project_id: projectId });
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: (_m, [p]) => {
|
||||
onCloseModal();
|
||||
if (isAgentParam(p)) {
|
||||
props?.onDeleteAgentSuccess?.(p);
|
||||
return;
|
||||
}
|
||||
if (isProjectParam(p)) {
|
||||
props?.onDeleteProjectSuccess?.(p);
|
||||
return;
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const onDelete = () => {
|
||||
if (!deleteParamsRef.current) {
|
||||
return;
|
||||
}
|
||||
return runAsync(deleteParamsRef.current);
|
||||
};
|
||||
|
||||
return {
|
||||
modalContextHolder: (
|
||||
<DeleteProjectModal
|
||||
maskClosable={false}
|
||||
value={name}
|
||||
onChange={setName}
|
||||
placeholder={deleteIntelligenceName}
|
||||
visible={visible}
|
||||
onCancel={onDeleteCancel}
|
||||
onOk={onDelete}
|
||||
okButtonProps={{
|
||||
disabled: deleteIntelligenceName !== name,
|
||||
loading,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
deleteIntelligence: ({
|
||||
name: deleteName,
|
||||
...restParam
|
||||
}: DeleteIntelligenceParam) => {
|
||||
setVisible(true);
|
||||
setDeleteIntelligenceName(deleteName);
|
||||
deleteParamsRef.current = restParam;
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 { localStorageService } from '@coze-foundation/local-storage';
|
||||
|
||||
const SESSION_HIDDEN_KEY = 'coze-project-entity-hidden-key';
|
||||
|
||||
export const useHiddenSession = (key: string) => {
|
||||
const [isSessionHidden, setIsSessionHidden] = useState(isKeyExist(key));
|
||||
return {
|
||||
isSessionHidden,
|
||||
hideSession: () => {
|
||||
if (isKeyExist(key)) {
|
||||
return;
|
||||
}
|
||||
const oldValue = localStorageService.getValue(SESSION_HIDDEN_KEY) || '';
|
||||
localStorageService.setValue(
|
||||
SESSION_HIDDEN_KEY,
|
||||
oldValue ? `${oldValue},${key}` : key,
|
||||
);
|
||||
setIsSessionHidden(true);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const isKeyExist = (key: string) => {
|
||||
const oldValue = localStorageService.getValue(SESSION_HIDDEN_KEY);
|
||||
return oldValue?.includes(key);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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';
|
||||
|
||||
export const useFormSubmitState = <T>({
|
||||
initialValues,
|
||||
getIsFormValid,
|
||||
}: {
|
||||
initialValues?: T;
|
||||
getIsFormValid: (values: T) => boolean;
|
||||
}) => {
|
||||
const [isFormValid, setFormValid] = useState(
|
||||
initialValues ? getIsFormValid(initialValues) : true,
|
||||
);
|
||||
const [isUploading, setUploading] = useState(false);
|
||||
|
||||
const checkFormValid = (values: T) => {
|
||||
setFormValid(getIsFormValid(values));
|
||||
};
|
||||
|
||||
const onValuesChange = (values: T) => {
|
||||
checkFormValid(values);
|
||||
};
|
||||
const onBeforeUpload = () => {
|
||||
setUploading(true);
|
||||
};
|
||||
|
||||
const onAfterUpload = () => {
|
||||
setUploading(false);
|
||||
};
|
||||
|
||||
return {
|
||||
isSubmitDisabled: !isFormValid || isUploading,
|
||||
checkFormValid,
|
||||
bizCallback: {
|
||||
onValuesChange,
|
||||
onBeforeUpload,
|
||||
onAfterUpload,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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 {
|
||||
appendCopySuffix,
|
||||
ProjectTemplateCopyModal,
|
||||
type ProjectTemplateCopyValue,
|
||||
} from '@coze-studio/components';
|
||||
import { ProductEntityType } from '@coze-arch/idl/product_api';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import {
|
||||
EVENT_NAMES,
|
||||
type ParamsTypeDefine,
|
||||
sendTeaEvent,
|
||||
} from '@coze-arch/bot-tea';
|
||||
import { 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 FormApi } from '@coze-arch/coze-design';
|
||||
|
||||
import { commonProjectFormValid } from '../utils/common-project-form-valid';
|
||||
import { useFormSubmitState } from './use-project-form-submit-state';
|
||||
|
||||
export type ProjectTemplateCopySuccessCallback = (param: {
|
||||
originProductId: string;
|
||||
newEntityId: string;
|
||||
toSpaceId: string;
|
||||
}) => void;
|
||||
|
||||
export type BeforeProjectTemplateCopyCallback = (params: {
|
||||
toSpaceId: string;
|
||||
}) => void;
|
||||
|
||||
export const useProjectTemplateCopyModal = (props: {
|
||||
onBefore?: BeforeProjectTemplateCopyCallback;
|
||||
onError?: () => void;
|
||||
onSuccess?: ProjectTemplateCopySuccessCallback;
|
||||
/** 埋点参数 - 当前页面/来源 */
|
||||
source: NonNullable<
|
||||
ParamsTypeDefine[EVENT_NAMES.template_action_front]['source']
|
||||
>;
|
||||
}) => {
|
||||
const [isSelectSpace, setSelectSpace] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [initValues, setInitValues] = useState<ProjectTemplateCopyValue>();
|
||||
const [sourceProduct, setSourceProduct] = useState<ProductInfo>();
|
||||
const formApi = useRef<FormApi<ProjectTemplateCopyValue>>();
|
||||
const {
|
||||
bizCallback: { onValuesChange },
|
||||
isSubmitDisabled,
|
||||
checkFormValid,
|
||||
} = useFormSubmitState<ProjectTemplateCopyValue>({
|
||||
getIsFormValid: values =>
|
||||
commonProjectFormValid(values) && Boolean(values.spaceId),
|
||||
});
|
||||
|
||||
const onModalClose = () => {
|
||||
setVisible(false);
|
||||
setInitValues(undefined);
|
||||
formApi.current = undefined;
|
||||
setSelectSpace(false);
|
||||
};
|
||||
|
||||
const { run, loading } = useRequest(
|
||||
async (copyRequestParam: ProjectTemplateCopyValue) => {
|
||||
const { productId, spaceId, name } = copyRequestParam;
|
||||
return ProductApi.PublicDuplicateProduct({
|
||||
product_id: productId,
|
||||
space_id: spaceId,
|
||||
name,
|
||||
entity_type: ProductEntityType.ProjectTemplate,
|
||||
});
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
onBefore: ([inputParam]) => {
|
||||
props.onBefore?.({ toSpaceId: inputParam.spaceId ?? '' });
|
||||
},
|
||||
onError: props.onError,
|
||||
onSuccess: (data, [inputParam]) => {
|
||||
onModalClose();
|
||||
sendTeaEvent(EVENT_NAMES.template_action_front, {
|
||||
template_id: sourceProduct?.meta_info.id || '',
|
||||
template_name: sourceProduct?.meta_info?.name || '',
|
||||
template_type: 'project',
|
||||
entity_id: sourceProduct?.meta_info.entity_id || '',
|
||||
entity_copy_id:
|
||||
sourceProduct?.project_extra?.template_project_id || '',
|
||||
template_tag_professional: sourceProduct?.meta_info.is_professional
|
||||
? 'professional'
|
||||
: 'basic',
|
||||
action: 'duplicate',
|
||||
after_id: data.data?.new_entity_id,
|
||||
source: props.source,
|
||||
...(sourceProduct?.meta_info?.is_free
|
||||
? ({
|
||||
template_tag_prize: 'free',
|
||||
} as const)
|
||||
: ({
|
||||
template_tag_prize: 'paid',
|
||||
template_prize_detail:
|
||||
Number(sourceProduct?.meta_info?.price?.amount) || 0,
|
||||
} as const)),
|
||||
});
|
||||
props?.onSuccess?.({
|
||||
originProductId: inputParam?.productId ?? '',
|
||||
newEntityId: data.data?.new_entity_id ?? '',
|
||||
toSpaceId: inputParam?.spaceId ?? '',
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
modalContextHolder: (
|
||||
<ProjectTemplateCopyModal
|
||||
title={I18n.t('creat_project_use_template')}
|
||||
isSelectSpace={isSelectSpace}
|
||||
visible={visible}
|
||||
okButtonProps={{
|
||||
disabled: isSubmitDisabled,
|
||||
loading,
|
||||
}}
|
||||
maskClosable={false}
|
||||
onOk={() => {
|
||||
const requestValues = formApi.current?.getValues();
|
||||
if (!requestValues) {
|
||||
throw new Error('duplicate project template values not provided');
|
||||
}
|
||||
run(requestValues);
|
||||
}}
|
||||
onCancel={onModalClose}
|
||||
formProps={{
|
||||
initValues,
|
||||
onValueChange: onValuesChange,
|
||||
getFormApi: api => {
|
||||
formApi.current = api;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
),
|
||||
copyProject: ({
|
||||
isSelectSpace: inputIsSelectSpace,
|
||||
sourceProduct: inputSourceProduct,
|
||||
...rest
|
||||
}: ProjectTemplateCopyValue & {
|
||||
isSelectSpace: boolean;
|
||||
/** 用于提取埋点参数 */
|
||||
sourceProduct: ProductInfo;
|
||||
}) => {
|
||||
setSelectSpace(inputIsSelectSpace);
|
||||
const fixedInitValues = {
|
||||
...rest,
|
||||
name: botInputLengthService.sliceStringByMaxLength({
|
||||
value: appendCopySuffix(rest.name),
|
||||
field: 'projectName',
|
||||
}),
|
||||
};
|
||||
setInitValues(fixedInitValues);
|
||||
checkFormValid(fixedInitValues);
|
||||
setSourceProduct(inputSourceProduct);
|
||||
setVisible(true);
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 ReactNode } from 'react';
|
||||
|
||||
import { type DraftProjectUpdateRequest } from '@coze-arch/idl/intelligence_api';
|
||||
import { type RenderAutoGenerateParams } from '@coze-common/biz-components/picture-upload';
|
||||
|
||||
import { type ModifyUploadValueType } from '../type';
|
||||
import {
|
||||
type UpdateProjectSuccessCallbackParam,
|
||||
useBaseUpdateOrCopyProjectModal,
|
||||
} from './use-base-update-or-copy-project-modal';
|
||||
|
||||
export const useUpdateProjectModalBase = ({
|
||||
onSuccess,
|
||||
renderAutoGenerate,
|
||||
}: {
|
||||
onSuccess?: (param: UpdateProjectSuccessCallbackParam) => void;
|
||||
renderAutoGenerate?: (params: RenderAutoGenerateParams) => React.ReactNode;
|
||||
}): {
|
||||
modalContextHolder: ReactNode;
|
||||
openModal: (param: {
|
||||
initialValue: ModifyUploadValueType<DraftProjectUpdateRequest>;
|
||||
}) => void;
|
||||
} =>
|
||||
useBaseUpdateOrCopyProjectModal({
|
||||
scene: 'update',
|
||||
onSuccess,
|
||||
renderAutoGenerate,
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 {
|
||||
useCreateProjectModalBase,
|
||||
type CreateProjectHookProps,
|
||||
} from './hooks/use-create-project-modal';
|
||||
export { useUpdateProjectModalBase } from './hooks/use-update-project-modal';
|
||||
export {
|
||||
useDeleteIntelligence,
|
||||
type DeleteIntelligenceParam,
|
||||
} from './hooks/use-delete-intelligence';
|
||||
export { useCopyProjectModalBase } from './hooks/use-copy-project-modal';
|
||||
export { type ProjectFormValues } from './components/project-form';
|
||||
|
||||
export {
|
||||
type UpdateProjectSuccessCallbackParam,
|
||||
type CopyProjectSuccessCallbackParam,
|
||||
} from './hooks/use-base-update-or-copy-project-modal';
|
||||
|
||||
export {
|
||||
type ModifyUploadValueType,
|
||||
type RequireCopyProjectRequest,
|
||||
} from './type';
|
||||
@@ -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 { type UploadValue } from '@coze-common/biz-components';
|
||||
|
||||
export type ModifyUploadValueType<T extends { icon_uri?: string }> = Omit<
|
||||
T,
|
||||
'icon_uri'
|
||||
> & { icon_uri?: UploadValue };
|
||||
|
||||
export type RequireCopyProjectRequest<
|
||||
T extends { project_id?: string; to_space_id?: string },
|
||||
> = Omit<T, 'project_id' | 'to_space_id'> & {
|
||||
project_id: string;
|
||||
to_space_id: string;
|
||||
};
|
||||
17
frontend/packages/studio/workspace/project-entity-base/src/typings.d.ts
vendored
Normal file
17
frontend/packages/studio/workspace/project-entity-base/src/typings.d.ts
vendored
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.
|
||||
*/
|
||||
|
||||
/// <reference types='@coze-arch/bot-typings' />
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import { type ProjectFormValues } from '../components/project-form';
|
||||
|
||||
export const commonProjectFormValid = (
|
||||
values: Pick<ProjectFormValues, 'name'>,
|
||||
) => Boolean(values.name?.trim());
|
||||
Reference in New Issue
Block a user