/* * 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, { useEffect, useState } from 'react'; import { useRequest } from 'ahooks'; import { ConnectorPublishStatus, PublishRecordStatus, type PublishRecordDetail, } from '@coze-arch/idl/intelligence_api'; import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n'; import { IconCozCheckMarkCircle, IconCozClock, IconCozCrossCircle, } from '@coze-arch/coze-design/icons'; import { Modal, Select, Tag, type TagProps } from '@coze-arch/coze-design'; import { type OptionProps } from '@coze-arch/bot-semi/Select'; import { intelligenceApi } from '@coze-arch/bot-api'; import { EProjectPermission, useProjectAuth } from '@coze-common/auth'; import { isPublishFinish } from '../utils/is-publish-finish'; import { ProjectPublishProgress } from '../publish-progress'; enum PublishStatus { Publishing, Failed, Success, } const PublishStatusMap: Record< PublishStatus, Pick & { text: I18nKeysNoOptionsType } > = { [PublishStatus.Publishing]: { prefixIcon: , color: 'brand', text: 'project_releasing', }, [PublishStatus.Failed]: { prefixIcon: , color: 'red', text: 'project_release_failed', }, [PublishStatus.Success]: { prefixIcon: , color: 'green', text: 'project_release_success', }, }; function toPublishStatus(record: PublishRecordDetail) { const projectFailed = record.publish_status === PublishRecordStatus.PackFailed || record.publish_status === PublishRecordStatus.AuditNotPass; const connectorsFailed = record.connector_publish_result?.some( item => item.connector_publish_status === ConnectorPublishStatus.Failed, ) ?? false; // The project itself failed, or some channels failed to publish - > overall failed if (projectFailed || connectorsFailed) { return PublishStatus.Failed; } const projectPublishing = record.publish_status === PublishRecordStatus.Packing || record.publish_status === PublishRecordStatus.Auditing || record.publish_status === PublishRecordStatus.ConnectorPublishing; const connectorsPublishing = record.connector_publish_result?.some( item => item.connector_publish_status === ConnectorPublishStatus.Default || item.connector_publish_status === ConnectorPublishStatus.Auditing, ) ?? false; // The project itself is being released, or some channels are being released - > the overall release is in progress if (projectPublishing || connectorsPublishing) { return PublishStatus.Publishing; } return PublishStatus.Success; } export interface ProjectPublishStatusProps { spaceId: string; projectId: string; defaultRecordID?: string; } /* eslint @coze-arch/max-line-per-function: ["error", {"max": 300}] */ export function usePublishStatus({ spaceId, projectId, defaultRecordID, }: ProjectPublishStatusProps) { const [status, setStatus] = useState(); const [latestRecord, setLatestRecord] = useState(); const [recordList, setRecordList] = useState([]); const [selectedVersion, setSelectedVersion] = useState(defaultRecordID); const [selectedRecord, setSelectedRecord] = useState(); const [modalVisible, setModalVisible] = useState(false); // Polling the latest release history until it stops when it is not in the "In Release" state const latestRecordRequest = useRequest( () => intelligenceApi.GetPublishRecordDetail({ project_id: projectId }), { manual: true, pollingInterval: 5000, pollingWhenHidden: false, pollingErrorRetryCount: 3, onSuccess: res => { const record = res.data; // Stop polling when no record is published if (!record || typeof record.publish_status !== 'number') { latestRecordRequest.cancel(); return; } setStatus(toPublishStatus(record)); setLatestRecord(record); // After first requesting the latest release record, its version number is selected by default if (!selectedVersion) { setRecordList([ { value: record.publish_record_id, label: record.version_number }, ]); setSelectedVersion(record.publish_record_id ?? ''); } else if (selectedVersion === record.publish_record_id) { setSelectedRecord(record); } if (isPublishFinish(record)) { latestRecordRequest.cancel(); } }, }, ); // Get a list of publication records const recordListRequest = useRequest( () => intelligenceApi.GetPublishRecordList({ project_id: projectId }), { manual: true, onSuccess: res => { setRecordList( res.data?.map(item => ({ value: item.publish_record_id, label: item.version_number, })) ?? [], ); }, }, ); const hasPermission = useProjectAuth( EProjectPermission.PUBLISH, projectId, spaceId, ); // When the user has "publish" permission, start polling useEffect(() => { if (!hasPermission || defaultRecordID) { return; } latestRecordRequest.run(); }, [hasPermission, defaultRecordID]); // Manually request selected release records const recordDetailRequest = useRequest( (recordId: string) => intelligenceApi.GetPublishRecordDetail({ project_id: projectId, publish_record_id: recordId, }), { manual: true, onSuccess: res => { const record = res.data; setSelectedRecord(record); if (record?.publish_record_id === latestRecord?.publish_record_id) { setLatestRecord(record); } }, }, ); const tagConfig = PublishStatusMap[status ?? PublishStatus.Failed]; const showingRecord = selectedRecord ?? latestRecord; const open = async () => { await recordListRequest.runAsync(); if (defaultRecordID) { await changeVersion(defaultRecordID); } setModalVisible(true); }; const close = () => { setModalVisible(false); }; const changeVersion = async (version: string) => { setSelectedVersion(version); await recordDetailRequest.run(version); }; return { latestVersion: latestRecord, currentVersion: recordList.find(item => item.value === selectedVersion), open, close, modal: ( setModalVisible(false)} >