fix(media): 视频任务 - 统一轮询超时并支持失败任务人工恢复
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { App, Descriptions, Drawer, Empty, Skeleton, Tabs, Tag, Typography } from "antd";
|
||||
import { App, Button, Descriptions, Drawer, Empty, Skeleton, Tabs, Tag, Typography } from "antd";
|
||||
import { RefreshCw } from "lucide-react";
|
||||
|
||||
import { getAdminApiLog, type ApiCallLog } from "@/services/api/auth";
|
||||
import { getAdminApiLog, queryAdminApiLogTask, type ApiCallLog } from "@/services/api/auth";
|
||||
|
||||
export function ApiLogDetailDrawer({ logId, onClose }: { logId: string | null; onClose: () => void }) {
|
||||
export function ApiLogDetailDrawer({ logId, onClose, onLogUpdated }: { logId: string | null; onClose: () => void; onLogUpdated?: (log: ApiCallLog) => void }) {
|
||||
const { message } = App.useApp();
|
||||
const [log, setLog] = useState<ApiCallLog | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [querying, setQuerying] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!logId) return;
|
||||
let active = true;
|
||||
@@ -19,14 +21,36 @@ export function ApiLogDetailDrawer({ logId, onClose }: { logId: string | null; o
|
||||
return () => { active = false; };
|
||||
}, [logId, message]);
|
||||
|
||||
const queryProviderTask = async () => {
|
||||
if (!log) return;
|
||||
setQuerying(true);
|
||||
try {
|
||||
const result = await queryAdminApiLogTask(log.id);
|
||||
const refreshed = await getAdminApiLog(log.id);
|
||||
setLog(refreshed.log);
|
||||
onLogUpdated?.(refreshed.log);
|
||||
if (result.recovered) {
|
||||
window.dispatchEvent(new CustomEvent("wallet:updated"));
|
||||
if (result.billingSettled) message.success("已获取上游视频,任务已恢复并完成结算");
|
||||
else message.warning("已获取上游视频,任务已恢复,计费状态待核对");
|
||||
} else {
|
||||
message.info(`上游任务仍在处理中${result.providerStatus ? `(${result.providerStatus})` : ""}`);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : "查询上游任务失败");
|
||||
} finally {
|
||||
setQuerying(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer title="请求详情" open={Boolean(logId)} onClose={onClose} size="min(920px, 100vw)" destroyOnHidden>
|
||||
{loading ? <Skeleton active paragraph={{ rows: 12 }} /> : log ? <LogDetail log={log} /> : <Empty description="没有请求详情" />}
|
||||
{loading ? <Skeleton active paragraph={{ rows: 12 }} /> : log ? <LogDetail log={log} querying={querying} onQueryProviderTask={queryProviderTask} /> : <Empty description="没有请求详情" />}
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
function LogDetail({ log }: { log: ApiCallLog }) {
|
||||
function LogDetail({ log, querying, onQueryProviderTask }: { log: ApiCallLog; querying: boolean; onQueryProviderTask: () => void }) {
|
||||
const providerStatus = log.providerStatus?.toLowerCase();
|
||||
const processing = ["queued", "pending", "processing", "running", "in_progress"].includes(providerStatus || "");
|
||||
const failed = log.status === "failed" || ["failed", "cancelled", "expired"].includes(providerStatus || "");
|
||||
@@ -49,7 +73,10 @@ function LogDetail({ log }: { log: ApiCallLog }) {
|
||||
["上游地址", log.upstreamUrl || "--"],
|
||||
].map(([label, children], index) => ({ key: String(index), label, children }));
|
||||
|
||||
const canQueryProviderTask = log.capability === "video" && log.taskStatus === "failed" && Boolean(log.taskId && log.providerRequestId);
|
||||
|
||||
return <div className="space-y-6">
|
||||
{canQueryProviderTask ? <div className="flex justify-end"><Button icon={<RefreshCw className="size-4" />} loading={querying} onClick={onQueryProviderTask}>手动查询任务</Button></div> : null}
|
||||
<Descriptions bordered size="small" column={{ xs: 1, sm: 1, md: 2 }} items={items} />
|
||||
<section>
|
||||
<div className="mb-2 text-sm font-semibold text-foreground/85">原始报文</div>
|
||||
|
||||
@@ -78,7 +78,7 @@ export default function LogsPage() {
|
||||
<TableSurface>
|
||||
{loading && logs.length === 0 ? <AdminTableSkeleton rows={8} columns={11} /> : <Table className="app-data-table" size="middle" rowKey="id" loading={loading} rowSelection={{ selectedRowKeys: selectedIds, preserveSelectedRowKeys: false, onChange: (keys) => setSelectedIds(keys.map(String)) }} columns={columns} dataSource={logs} locale={{ emptyText: <AdminTableEmpty filtered={hasFilters} /> }} pagination={{ current: page, pageSize, total, showSizeChanger: true, pageSizeOptions: [20, 50, 100], showTotal: (value, range) => `${range[0]}-${range[1]} / 共 ${value} 条`, onChange: (nextPage, nextSize) => updateUrl({ page: nextSize !== pageSize ? 1 : nextPage, pageSize: nextSize }) }} scroll={{ x: 1700 }} />}
|
||||
</TableSurface>
|
||||
<ApiLogDetailDrawer logId={detailLogId} onClose={() => setDetailLogId(null)} />
|
||||
<ApiLogDetailDrawer logId={detailLogId} onClose={() => setDetailLogId(null)} onLogUpdated={(next) => setLogs((items) => items.map((item) => item.id === next.id ? next : item))} />
|
||||
<Modal title={mediaPreview?.title || "媒体预览"} open={Boolean(mediaPreview)} width={880} onCancel={() => setMediaPreview(null)} footer={mediaPreview ? <Button icon={<Download className="size-4" />} onClick={() => downloadMedia(mediaPreview.url, mediaPreview.kind)}>下载原文件</Button> : null} destroyOnHidden>
|
||||
{mediaPreview?.kind === "video" ? <video src={mediaPreview.url} controls playsInline preload="metadata" className="max-h-[72vh] w-full bg-black object-contain" /> : mediaPreview ? <img src={mediaPreview.url} alt={mediaPreview.title} className="max-h-[72vh] w-full bg-black object-contain" /> : null}
|
||||
</Modal>
|
||||
|
||||
Reference in New Issue
Block a user