324 lines
9.6 KiB
TypeScript
324 lines
9.6 KiB
TypeScript
/*
|
|
* 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, { useLayoutEffect, useRef, useState } from 'react';
|
|
|
|
import {
|
|
ConnectorPublishStatus,
|
|
type PublishRecordDetail,
|
|
PublishRecordStatus,
|
|
ResourceType,
|
|
} from '@coze-arch/idl/intelligence_api';
|
|
import { I18n } from '@coze-arch/i18n';
|
|
import { IconCozPlugin, IconCozWorkflow } from '@coze-arch/coze-design/icons';
|
|
import {
|
|
type StepProps,
|
|
Steps,
|
|
TagGroup,
|
|
Typography,
|
|
} from '@coze-arch/coze-design';
|
|
import { type DynamicParams } from '@coze-arch/bot-typings/teamspace';
|
|
import { type UITagProps } from '@coze-arch/bot-semi';
|
|
import { useParams } from 'react-router-dom';
|
|
|
|
import { useWebSdkGuideModal } from '@/web-sdk-guide';
|
|
import { WEB_SDK_CONNECTOR_ID } from '@/utils/constants';
|
|
import { type ProjectPublishStore } from '@/store';
|
|
|
|
import { PublishStepTitle } from './components/publish-step-title';
|
|
import { PublishStepIcon } from './components/publish-step-icon';
|
|
import {
|
|
ConnectorStatus,
|
|
type ConnectorStatusProps,
|
|
} from './components/connector-status';
|
|
|
|
function getDefaultStepProps(title: string): StepProps {
|
|
return {
|
|
icon: <PublishStepIcon status="wait" />,
|
|
title: <PublishStepTitle title={title} />,
|
|
};
|
|
}
|
|
|
|
function toPackStepProps(
|
|
record: PublishRecordDetail,
|
|
tagGroupRef: React.RefObject<HTMLDivElement>,
|
|
maxTagCount?: number,
|
|
): StepProps {
|
|
const title = I18n.t('project_release_package');
|
|
if (typeof record.publish_status !== 'number') {
|
|
return getDefaultStepProps(title);
|
|
}
|
|
switch (record.publish_status) {
|
|
case PublishRecordStatus.Packing: {
|
|
return {
|
|
icon: <PublishStepIcon status="process" />,
|
|
title: (
|
|
<PublishStepTitle
|
|
title={title}
|
|
tag={I18n.t('project_release_in_progress')}
|
|
color="brand"
|
|
/>
|
|
),
|
|
};
|
|
}
|
|
case PublishRecordStatus.PackFailed: {
|
|
const tags: UITagProps[] | undefined =
|
|
record.publish_status_detail?.pack_failed_detail?.map(item => ({
|
|
tagKey: item.entity_id,
|
|
className: 'pack-status-tag',
|
|
prefixIcon:
|
|
item.entity_type === ResourceType.Workflow ? (
|
|
<IconCozWorkflow />
|
|
) : (
|
|
<IconCozPlugin />
|
|
),
|
|
children: item.entity_name,
|
|
}));
|
|
return {
|
|
icon: <PublishStepIcon status="error" />,
|
|
title: (
|
|
<PublishStepTitle
|
|
title={title}
|
|
tag={I18n.t('project_release_package_failed')}
|
|
color="red"
|
|
/>
|
|
),
|
|
description: tags ? (
|
|
<div ref={tagGroupRef}>
|
|
<Typography.Paragraph className="coz-fg-secondary mb-[4px]">
|
|
{I18n.t('project_release_pack_fail_reason')}
|
|
</Typography.Paragraph>
|
|
<TagGroup
|
|
tagList={tags}
|
|
maxTagCount={maxTagCount}
|
|
showPopover
|
|
popoverProps={{
|
|
position: 'top',
|
|
style: {
|
|
padding: 8,
|
|
backgroundColor: 'rgb(var(--black-5))',
|
|
borderColor: 'rgb(var(--black-5))',
|
|
},
|
|
}}
|
|
/>
|
|
</div>
|
|
) : null,
|
|
};
|
|
}
|
|
default: {
|
|
return {
|
|
icon: <PublishStepIcon status="finish" />,
|
|
title: (
|
|
<PublishStepTitle
|
|
title={title}
|
|
tag={I18n.t('project_release_finish')}
|
|
color="green"
|
|
/>
|
|
),
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
function PackStep(props: { record: PublishRecordDetail }) {
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
const [maxTagCount, setMaxTagCount] = useState<number | undefined>(undefined);
|
|
const stepProps = toPackStepProps(props.record, ref, maxTagCount);
|
|
|
|
/**
|
|
* TagGroup 仅支持设置 maxTagCount 控制展示 Tag 的数量,不支持设置展示行数。
|
|
* 这里通过遍历所有 Tag 的 offsetTop 来判断其所在的行数,并将 maxTagCount
|
|
* 设置为刚好能展示两行的数量。
|
|
*/
|
|
useLayoutEffect(() => {
|
|
if (!ref.current) {
|
|
return;
|
|
}
|
|
const tags = ref.current.getElementsByClassName(
|
|
'pack-status-tag',
|
|
) as HTMLCollectionOf<HTMLElement>;
|
|
if (tags.length <= 0) {
|
|
return;
|
|
}
|
|
let top = -1;
|
|
let rowCount = 0;
|
|
for (let i = 0; i < tags.length; i++) {
|
|
const tagTop = tags[i].offsetTop;
|
|
if (top !== tagTop) {
|
|
top = tagTop;
|
|
rowCount++;
|
|
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- offsetTop 第三次变化,当前 Tag 处于第三行
|
|
if (rowCount >= 3) {
|
|
setMaxTagCount(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}, [props.record.publish_status_detail?.pack_failed_detail]);
|
|
|
|
return <Steps.Step {...stepProps} />;
|
|
}
|
|
|
|
function toAuditStepProps(record: PublishRecordDetail): StepProps {
|
|
const title = I18n.t('project_release_coze_audit');
|
|
if (typeof record.publish_status !== 'number') {
|
|
return getDefaultStepProps(title);
|
|
}
|
|
switch (record.publish_status) {
|
|
case PublishRecordStatus.Packing:
|
|
case PublishRecordStatus.PackFailed: {
|
|
return getDefaultStepProps(title);
|
|
}
|
|
case PublishRecordStatus.Auditing: {
|
|
return {
|
|
status: 'process',
|
|
icon: <PublishStepIcon status="process" />,
|
|
title: (
|
|
<PublishStepTitle
|
|
title={title}
|
|
tag={I18n.t('project_release_in_progress')}
|
|
color="brand"
|
|
/>
|
|
),
|
|
};
|
|
}
|
|
case PublishRecordStatus.AuditNotPass: {
|
|
return {
|
|
status: 'error',
|
|
icon: <PublishStepIcon status="error" />,
|
|
title: (
|
|
<PublishStepTitle
|
|
title={title}
|
|
tag={I18n.t('project_release_not_pass')}
|
|
color="red"
|
|
/>
|
|
),
|
|
};
|
|
}
|
|
default: {
|
|
return {
|
|
status: 'finish',
|
|
icon: <PublishStepIcon status="finish" />,
|
|
title: (
|
|
<PublishStepTitle
|
|
title={title}
|
|
tag={I18n.t('project_release_pass')}
|
|
color="green"
|
|
/>
|
|
),
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
function getConnectorsPublishStatus(
|
|
record: ProjectPublishStore['publishRecordDetail'],
|
|
) {
|
|
const connectorResults = record.connector_publish_result ?? [];
|
|
if (connectorResults.length <= 0) {
|
|
return 'wait';
|
|
}
|
|
const failedCount = connectorResults.filter(
|
|
item => item.connector_publish_status === ConnectorPublishStatus.Failed,
|
|
).length;
|
|
if (failedCount > 0) {
|
|
// 所有渠道均失败,显示红色叉号;部分渠道失败,显示黄色感叹号
|
|
return failedCount === connectorResults.length ? 'error' : 'warn';
|
|
}
|
|
const publishingCount = connectorResults.filter(
|
|
item =>
|
|
item.connector_publish_status === ConnectorPublishStatus.Default ||
|
|
item.connector_publish_status === ConnectorPublishStatus.Auditing,
|
|
).length;
|
|
if (publishingCount > 0) {
|
|
// 部分渠道在发布中,显示时钟图标
|
|
return 'process';
|
|
}
|
|
return 'finish';
|
|
}
|
|
|
|
function toPublishStepProps(
|
|
record: ProjectPublishStore['publishRecordDetail'],
|
|
onShowWebSdkGuide: ConnectorStatusProps['onShowWebSdkGuide'],
|
|
): StepProps {
|
|
const title = I18n.t('project_release_channel');
|
|
if (typeof record.publish_status !== 'number') {
|
|
return getDefaultStepProps(title);
|
|
}
|
|
// 未到达“渠道审核与发布”步骤,显示默认的灰色时钟图标
|
|
if (record.publish_status < PublishRecordStatus.ConnectorPublishing) {
|
|
return {
|
|
...getDefaultStepProps(title),
|
|
description: record.connector_publish_result?.map(item => (
|
|
<ConnectorStatus result={item} showTag={false} />
|
|
)),
|
|
};
|
|
}
|
|
return {
|
|
icon: <PublishStepIcon status={getConnectorsPublishStatus(record)} />,
|
|
title: (
|
|
<PublishStepTitle
|
|
title={title}
|
|
{...(IS_OVERSEA &&
|
|
// publish_monetization_result 为 nil 表示可能是接口失败,也不展示
|
|
record.publish_monetization_result === false && {
|
|
tag: I18n.t('monetization_publish_fail'),
|
|
color: 'red',
|
|
})}
|
|
/>
|
|
),
|
|
description: record.connector_publish_result?.map(item => (
|
|
<ConnectorStatus result={item} onShowWebSdkGuide={onShowWebSdkGuide} />
|
|
)),
|
|
};
|
|
}
|
|
|
|
function PublishStep(props: { record: PublishRecordDetail }) {
|
|
const { project_id = '' } = useParams<DynamicParams>();
|
|
const { node, show: showWebSdkGuideModal } = useWebSdkGuideModal();
|
|
const onShowWebSdkGuide = (workflowId: string) =>
|
|
showWebSdkGuideModal({
|
|
projectId: project_id,
|
|
workflowId,
|
|
version: props.record.connector_publish_result?.find(
|
|
item => item.connector_id === WEB_SDK_CONNECTOR_ID,
|
|
)?.connector_bind_info?.sdk_version,
|
|
});
|
|
const stepProps = toPublishStepProps(props.record, onShowWebSdkGuide);
|
|
return (
|
|
<>
|
|
<Steps.Step {...stepProps} />
|
|
{node}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export interface ProjectPublishProgressProps {
|
|
record: ProjectPublishStore['publishRecordDetail'];
|
|
}
|
|
|
|
export function ProjectPublishProgress({
|
|
record,
|
|
}: ProjectPublishProgressProps) {
|
|
return (
|
|
<Steps type="basic" direction="vertical" size="small">
|
|
<PackStep record={record} />
|
|
<Steps.Step {...toAuditStepProps(record)} />
|
|
<PublishStep record={record} />
|
|
</Steps>
|
|
);
|
|
}
|