celery 容灾升级
This commit is contained in:
@@ -7,7 +7,6 @@ import {
|
||||
Descriptions,
|
||||
Drawer,
|
||||
Empty,
|
||||
Input,
|
||||
Select,
|
||||
Space,
|
||||
Spin,
|
||||
@@ -23,6 +22,7 @@ import { getAdminShotSegmentDetail, getAdminShotSegments, getAdminShotTaskSetDet
|
||||
import type { ShotAiSuggestionOut, ShotSegmentDetailOut, ShotSegmentOut, ShotTaskSetDetailOut } from '../types';
|
||||
import { formatDate } from '../utils/formatDate';
|
||||
import { getStepCodeLabel } from './adminReplication/components/StatusTag';
|
||||
import { getShotAnalysisStatusMeta, getShotReplicateStatusMeta, getShotSplitStatusMeta, getShotTaskStatusMeta } from '../utils/shotReplicateStatus';
|
||||
|
||||
const RAW_API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:8000';
|
||||
const RESOURCE_BASE = RAW_API_BASE.replace(/\/api\/?$/i, '').replace(/\/$/, '');
|
||||
@@ -55,25 +55,6 @@ const REPLICATE_STATUS_OPTIONS = [
|
||||
{ value: 'failed', label: '复刻失败' },
|
||||
];
|
||||
|
||||
const STATUS_MAP: Record<string, { color: string; text: string }> = {
|
||||
pending_analysis: { color: 'default', text: '等待分析' },
|
||||
analyzing: { color: 'processing', text: '分析中' },
|
||||
analysis_completed: { color: 'success', text: '分析完成' },
|
||||
analysis_failed: { color: 'error', text: '分析失败' },
|
||||
splitting: { color: 'warning', text: '拆镜中' },
|
||||
split_completed: { color: 'success', text: '拆镜完成' },
|
||||
partial_failed: { color: 'orange', text: '部分失败' },
|
||||
failed: { color: 'error', text: '失败' },
|
||||
none: { color: 'default', text: '未拆镜' },
|
||||
pending: { color: 'default', text: '待处理' },
|
||||
processing: { color: 'warning', text: '处理中' },
|
||||
completed: { color: 'success', text: '完成' },
|
||||
retry_waiting: { color: 'orange', text: '等待重试' },
|
||||
not_required: { color: 'default', text: '无需分析' },
|
||||
not_started: { color: 'default', text: '未复刻' },
|
||||
project_created: { color: 'processing', text: '已创建项目' },
|
||||
};
|
||||
|
||||
const apiUrl = (url?: string | null): string => {
|
||||
if (!url) return '';
|
||||
const value = String(url).trim();
|
||||
@@ -85,9 +66,13 @@ const apiUrl = (url?: string | null): string => {
|
||||
const safeDate = (value?: string | null): string => (value ? formatDate(value) : '-');
|
||||
const shortId = (value?: string | null): string => (!value ? '-' : value.length > 16 ? `${value.slice(0, 10)}...` : value);
|
||||
|
||||
const StatusTag: React.FC<{ status?: string | null }> = ({ status }) => {
|
||||
const StatusTag: React.FC<{ status?: string | null; kind?: 'task' | 'analysis' | 'split' | 'replicate' }> = ({ status, kind = 'task' }) => {
|
||||
if (!status) return <Tag>-</Tag>;
|
||||
const meta = STATUS_MAP[status] || { color: 'blue', text: status };
|
||||
const meta = kind === 'analysis'
|
||||
? getShotAnalysisStatusMeta(status)
|
||||
: (kind === 'split'
|
||||
? getShotSplitStatusMeta(status)
|
||||
: (kind === 'replicate' ? getShotReplicateStatusMeta(status) : getShotTaskStatusMeta(status)));
|
||||
return <Tag color={meta.color}>{meta.text}</Tag>;
|
||||
};
|
||||
|
||||
@@ -177,6 +162,32 @@ const AdminShotTaskSetDetail: React.FC = () => {
|
||||
useEffect(() => { loadDetail(); }, [loadDetail, reloadKey]);
|
||||
useEffect(() => { loadSegments(); }, [loadSegments, reloadKey]);
|
||||
|
||||
const hasActiveTasks = Boolean(detail && (
|
||||
getShotTaskStatusMeta(detail.status).active
|
||||
|| getShotAnalysisStatusMeta(detail.analysisStatus).active
|
||||
|| getShotSplitStatusMeta(detail.splitStatus).active
|
||||
|| segments.some((item) => (
|
||||
getShotSplitStatusMeta(item.splitStatus).active
|
||||
|| getShotAnalysisStatusMeta(item.analysisStatus).active
|
||||
|| getShotReplicateStatusMeta(item.replicateStatus).active
|
||||
))
|
||||
));
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasActiveTasks) return undefined;
|
||||
const refresh = () => {
|
||||
if (document.visibilityState !== 'visible') return;
|
||||
void loadDetail();
|
||||
void loadSegments();
|
||||
};
|
||||
const timer = window.setInterval(refresh, 20000);
|
||||
document.addEventListener('visibilitychange', refresh);
|
||||
return () => {
|
||||
window.clearInterval(timer);
|
||||
document.removeEventListener('visibilitychange', refresh);
|
||||
};
|
||||
}, [hasActiveTasks, loadDetail, loadSegments]);
|
||||
|
||||
const openSegmentDetail = async (segmentId: string) => {
|
||||
setDrawerOpen(true);
|
||||
setSegmentDetail(null);
|
||||
@@ -224,9 +235,9 @@ const AdminShotTaskSetDetail: React.FC = () => {
|
||||
<Descriptions.Item label="用户名">{detail.userName || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="标题">{detail.title || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="视频时长">{Number(detail.videoDurationSeconds || 0).toFixed(2)}s</Descriptions.Item>
|
||||
<Descriptions.Item label="总状态"><StatusTag status={detail.status} /></Descriptions.Item>
|
||||
<Descriptions.Item label="分析状态"><StatusTag status={detail.analysisStatus} /></Descriptions.Item>
|
||||
<Descriptions.Item label="拆镜状态"><StatusTag status={detail.splitStatus} /></Descriptions.Item>
|
||||
<Descriptions.Item label="总状态"><StatusTag status={detail.status} kind="task" /></Descriptions.Item>
|
||||
<Descriptions.Item label="分析状态"><StatusTag status={detail.analysisStatus} kind="analysis" /></Descriptions.Item>
|
||||
<Descriptions.Item label="拆镜状态"><StatusTag status={detail.splitStatus} kind="split" /></Descriptions.Item>
|
||||
<Descriptions.Item label="片段数量">{detail.completedSegmentCount}/{detail.segmentCount},失败 {detail.failedSegmentCount}</Descriptions.Item>
|
||||
<Descriptions.Item label="原视频分类">{detail.originalVideoCategory || '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="创建时间">{safeDate(detail.createdAt)}</Descriptions.Item>
|
||||
@@ -274,9 +285,9 @@ const AdminShotTaskSetDetail: React.FC = () => {
|
||||
{ title: '来源', dataIndex: 'sourceMode', width: 100, render: (v: string) => v === 'ai_suggestion' ? <Tag color="purple">AI建议</Tag> : <Tag color="cyan">自定义</Tag> },
|
||||
{ title: '时间节点', dataIndex: 'timeNode', width: 130 },
|
||||
{ title: '时长', dataIndex: 'durationSeconds', width: 90, render: (v: number) => `${Number(v || 0).toFixed(2)}s` },
|
||||
{ title: '切割', dataIndex: 'splitStatus', width: 100, render: (v: string) => <StatusTag status={v} /> },
|
||||
{ title: '分析', dataIndex: 'analysisStatus', width: 100, render: (v: string) => <StatusTag status={v} /> },
|
||||
{ title: '复刻', dataIndex: 'replicateStatus', width: 110, render: (v: string) => <StatusTag status={v} /> },
|
||||
{ title: '切割', dataIndex: 'splitStatus', width: 100, render: (v: string) => <StatusTag status={v} kind="split" /> },
|
||||
{ title: '分析', dataIndex: 'analysisStatus', width: 100, render: (v: string) => <StatusTag status={v} kind="analysis" /> },
|
||||
{ title: '复刻', dataIndex: 'replicateStatus', width: 110, render: (v: string) => <StatusTag status={v} kind="replicate" /> },
|
||||
{ title: '片段内容', dataIndex: 'segmentContent', width: 260, ellipsis: true, render: (v: string) => v || '-' },
|
||||
{ title: '分类', dataIndex: 'segmentCategory', width: 120, render: (v: string) => v || '-' },
|
||||
{
|
||||
@@ -287,7 +298,7 @@ const AdminShotTaskSetDetail: React.FC = () => {
|
||||
<Button type="link" style={{ padding: 0 }} onClick={() => navigate(`/shot-replications/projects/${record.moduleProjectId}?flow_version=${record.moduleProjectFlowVersion === 'v2' ? 'v2' : 'v1'}`)}>{shortId(record.moduleProjectId)}</Button>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>{record.moduleProjectTitle || getStepCodeLabel(record.moduleProjectCurrentStepCode)}</Typography.Text>
|
||||
<Space size={4}>
|
||||
<StatusTag status={record.moduleProjectStatus} />
|
||||
<StatusTag status={record.moduleProjectStatus} kind="replicate" />
|
||||
<Tag color={record.moduleProjectFlowVersion === 'v2' ? 'blue' : 'default'}>
|
||||
{String(record.moduleProjectFlowVersion || 'v1').toUpperCase()}
|
||||
</Tag>
|
||||
@@ -315,9 +326,9 @@ const AdminShotTaskSetDetail: React.FC = () => {
|
||||
<Descriptions.Item label="片段ID" span={2}>{segmentDetail.id}</Descriptions.Item>
|
||||
<Descriptions.Item label="时间节点">{segmentDetail.timeNode}</Descriptions.Item>
|
||||
<Descriptions.Item label="时长">{Number(segmentDetail.durationSeconds || 0).toFixed(2)}s</Descriptions.Item>
|
||||
<Descriptions.Item label="切割状态"><StatusTag status={segmentDetail.splitStatus} /></Descriptions.Item>
|
||||
<Descriptions.Item label="分析状态"><StatusTag status={segmentDetail.analysisStatus} /></Descriptions.Item>
|
||||
<Descriptions.Item label="复刻状态"><StatusTag status={segmentDetail.replicateStatus} /></Descriptions.Item>
|
||||
<Descriptions.Item label="切割状态"><StatusTag status={segmentDetail.splitStatus} kind="split" /></Descriptions.Item>
|
||||
<Descriptions.Item label="分析状态"><StatusTag status={segmentDetail.analysisStatus} kind="analysis" /></Descriptions.Item>
|
||||
<Descriptions.Item label="复刻状态"><StatusTag status={segmentDetail.replicateStatus} kind="replicate" /></Descriptions.Item>
|
||||
<Descriptions.Item label="关联项目">{segmentDetail.moduleProjectId ? <Button type="link" onClick={() => navigate(`/shot-replications/projects/${segmentDetail.moduleProjectId}?flow_version=${segmentDetail.moduleProjectFlowVersion === 'v2' ? 'v2' : 'v1'}`)}>{segmentDetail.moduleProjectId}</Button> : '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="项目流程版本">{segmentDetail.moduleProjectId ? <Tag color={segmentDetail.moduleProjectFlowVersion === 'v2' ? 'blue' : 'default'}>{String(segmentDetail.moduleProjectFlowVersion || 'v1').toUpperCase()}</Tag> : '-'}</Descriptions.Item>
|
||||
<Descriptions.Item label="片段内容" span={2}>{segmentDetail.segmentContent || '-'}</Descriptions.Item>
|
||||
|
||||
Reference in New Issue
Block a user