|
@@ -27,9 +27,10 @@ import {
|
|
|
PictureOutlined,
|
|
PictureOutlined,
|
|
|
PlayCircleOutlined,
|
|
PlayCircleOutlined,
|
|
|
MobileOutlined,
|
|
MobileOutlined,
|
|
|
|
|
+ CloudSyncOutlined,
|
|
|
} from "@ant-design/icons";
|
|
} from "@ant-design/icons";
|
|
|
-import type { AiOrderV2Item, AiOrderV2Params } from "@/api/aiOrderV2";
|
|
|
|
|
-import { getAiOrderV2List, getAiOrderV2Detail } from "@/api/aiOrderV2";
|
|
|
|
|
|
|
+import type { AiOrderV2Item, AiOrderV2Params, WorkflowTaskMessage } from "@/api/aiOrderV2";
|
|
|
|
|
+import { getAiOrderV2List, getAiOrderV2Detail, queryAiOrderV2Result } from "@/api/aiOrderV2";
|
|
|
|
|
|
|
|
const { Title } = Typography;
|
|
const { Title } = Typography;
|
|
|
|
|
|
|
@@ -208,6 +209,11 @@ export function AiOrderV2Page() {
|
|
|
const [detailData, setDetailData] = useState<AiOrderV2Item | null>(null);
|
|
const [detailData, setDetailData] = useState<AiOrderV2Item | null>(null);
|
|
|
const [detailId, setDetailId] = useState<string>("");
|
|
const [detailId, setDetailId] = useState<string>("");
|
|
|
|
|
|
|
|
|
|
+ const [queryResultOpen, setQueryResultOpen] = useState(false);
|
|
|
|
|
+ const [queryResultLoading, setQueryResultLoading] = useState(false);
|
|
|
|
|
+ const [queryResultData, setQueryResultData] = useState<WorkflowTaskMessage | null>(null);
|
|
|
|
|
+ const [queryResultTaskId, setQueryResultTaskId] = useState<string>("");
|
|
|
|
|
+
|
|
|
const fetchData = useCallback(async () => {
|
|
const fetchData = useCallback(async () => {
|
|
|
setLoading(true);
|
|
setLoading(true);
|
|
|
try {
|
|
try {
|
|
@@ -267,6 +273,41 @@ export function AiOrderV2Page() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ async function handleQueryResult(taskId?: string) {
|
|
|
|
|
+ if (!taskId) {
|
|
|
|
|
+ message.warning(t("aiOrderV2.queryResultNoTaskId"));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ setQueryResultOpen(true);
|
|
|
|
|
+ setQueryResultTaskId(taskId);
|
|
|
|
|
+ setQueryResultData(null);
|
|
|
|
|
+ setQueryResultLoading(true);
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await queryAiOrderV2Result(taskId);
|
|
|
|
|
+ setQueryResultData(res);
|
|
|
|
|
+ message.success(t("aiOrderV2.queryResultSuccess"));
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ message.error(t("aiOrderV2.queryResultFailed"));
|
|
|
|
|
+ setQueryResultData(null);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setQueryResultLoading(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const QUERY_STATUS_MAP: Record<string, { i18nKey: string; color: string }> = {
|
|
|
|
|
+ successes: { i18nKey: "aiOrderV2.queryStatusSuccesses", color: "green" },
|
|
|
|
|
+ failed: { i18nKey: "aiOrderV2.queryStatusFailed", color: "red" },
|
|
|
|
|
+ running: { i18nKey: "aiOrderV2.queryStatusRunning", color: "blue" },
|
|
|
|
|
+ pending: { i18nKey: "aiOrderV2.queryStatusPending", color: "gold" },
|
|
|
|
|
+ processing: { i18nKey: "aiOrderV2.queryStatusProcessing", color: "cyan" },
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ function formatTimestamp(ts?: number) {
|
|
|
|
|
+ if (!ts) return "—";
|
|
|
|
|
+ const d = new Date(ts * 1000);
|
|
|
|
|
+ return d.toLocaleString();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
const columns: TableProps<AiOrderV2Item>["columns"] = [
|
|
const columns: TableProps<AiOrderV2Item>["columns"] = [
|
|
|
{
|
|
{
|
|
|
title: t("aiOrderV2.colOrder"),
|
|
title: t("aiOrderV2.colOrder"),
|
|
@@ -542,18 +583,29 @@ export function AiOrderV2Page() {
|
|
|
{
|
|
{
|
|
|
title: t("aiOrderV2.colActions"),
|
|
title: t("aiOrderV2.colActions"),
|
|
|
key: "actions",
|
|
key: "actions",
|
|
|
- width: 90,
|
|
|
|
|
|
|
+ width: 150,
|
|
|
fixed: "right",
|
|
fixed: "right",
|
|
|
align: "center",
|
|
align: "center",
|
|
|
render: (_, r) => (
|
|
render: (_, r) => (
|
|
|
- <Button
|
|
|
|
|
- type="link"
|
|
|
|
|
- size="small"
|
|
|
|
|
- icon={<EyeOutlined />}
|
|
|
|
|
- onClick={() => handleOpenDetail(r.id)}
|
|
|
|
|
- >
|
|
|
|
|
- {t("aiOrderV2.viewDetail")}
|
|
|
|
|
- </Button>
|
|
|
|
|
|
|
+ <div className="flex flex-col items-center gap-1">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ type="link"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ icon={<EyeOutlined />}
|
|
|
|
|
+ onClick={() => handleOpenDetail(r.id)}
|
|
|
|
|
+ >
|
|
|
|
|
+ {t("aiOrderV2.viewDetail")}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ type="link"
|
|
|
|
|
+ size="small"
|
|
|
|
|
+ icon={<CloudSyncOutlined />}
|
|
|
|
|
+ onClick={() => handleQueryResult(r.taskId)}
|
|
|
|
|
+ disabled={!r.taskId}
|
|
|
|
|
+ >
|
|
|
|
|
+ {t("aiOrderV2.queryResult")}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
),
|
|
),
|
|
|
},
|
|
},
|
|
|
];
|
|
];
|
|
@@ -802,6 +854,122 @@ export function AiOrderV2Page() {
|
|
|
<span className="text-gray-300">—</span>
|
|
<span className="text-gray-300">—</span>
|
|
|
)}
|
|
)}
|
|
|
</Modal>
|
|
</Modal>
|
|
|
|
|
+
|
|
|
|
|
+ <Modal
|
|
|
|
|
+ title={t("aiOrderV2.queryResultTitle", { taskId: queryResultTaskId })}
|
|
|
|
|
+ open={queryResultOpen}
|
|
|
|
|
+ onCancel={() => setQueryResultOpen(false)}
|
|
|
|
|
+ footer={null}
|
|
|
|
|
+ width={720}
|
|
|
|
|
+ >
|
|
|
|
|
+ {queryResultLoading ? (
|
|
|
|
|
+ <div className="py-8 text-center text-gray-500">
|
|
|
|
|
+ {t("aiOrderV2.queryResultLoading")}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : queryResultData ? (
|
|
|
|
|
+ <div className="space-y-3">
|
|
|
|
|
+ <Descriptions bordered size="small" column={2}>
|
|
|
|
|
+ <Descriptions.Item label={t("aiOrderV2.queryFieldTaskId")}>
|
|
|
|
|
+ {queryResultData.taskId || "—"}
|
|
|
|
|
+ </Descriptions.Item>
|
|
|
|
|
+ <Descriptions.Item label={t("aiOrderV2.queryFieldStatus")}>
|
|
|
|
|
+ {queryResultData.status ? (() => {
|
|
|
|
|
+ const cfg = QUERY_STATUS_MAP[queryResultData.status];
|
|
|
|
|
+ return cfg ? (
|
|
|
|
|
+ <Tag color={cfg.color} style={{ margin: 0 }}>
|
|
|
|
|
+ {t(cfg.i18nKey)}
|
|
|
|
|
+ </Tag>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <Tag style={{ margin: 0 }}>{queryResultData.status}</Tag>
|
|
|
|
|
+ );
|
|
|
|
|
+ })() : "—"}
|
|
|
|
|
+ </Descriptions.Item>
|
|
|
|
|
+ <Descriptions.Item label={t("aiOrderV2.queryFieldType")}>
|
|
|
|
|
+ {queryResultData.type || "—"}
|
|
|
|
|
+ </Descriptions.Item>
|
|
|
|
|
+ <Descriptions.Item label={t("aiOrderV2.queryFieldMessage")}>
|
|
|
|
|
+ {queryResultData.message || "—"}
|
|
|
|
|
+ </Descriptions.Item>
|
|
|
|
|
+ <Descriptions.Item label={t("aiOrderV2.queryFieldCreateTime")}>
|
|
|
|
|
+ {formatTimestamp(queryResultData.createTime)}
|
|
|
|
|
+ </Descriptions.Item>
|
|
|
|
|
+ <Descriptions.Item label={t("aiOrderV2.queryFieldUpdateTime")}>
|
|
|
|
|
+ {formatTimestamp(queryResultData.updateTime)}
|
|
|
|
|
+ </Descriptions.Item>
|
|
|
|
|
+ </Descriptions>
|
|
|
|
|
+
|
|
|
|
|
+ {queryResultData.request && (
|
|
|
|
|
+ <Card size="small" title={t("aiOrderV2.queryFieldTemplate")}>
|
|
|
|
|
+ <div className="space-y-2">
|
|
|
|
|
+ <div className="text-sm">
|
|
|
|
|
+ <span className="text-gray-500">{t("aiOrderV2.queryFieldTemplate")}:</span>{" "}
|
|
|
|
|
+ <span className="font-medium">{queryResultData.request.template || "—"}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ {queryResultData.request.inputs && (
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div className="text-sm text-gray-500 mb-1">{t("aiOrderV2.queryFieldInputs")}:</div>
|
|
|
|
|
+ <pre className="m-0 text-xs bg-gray-50 rounded p-2 whitespace-pre-wrap">
|
|
|
|
|
+ {JSON.stringify(queryResultData.request.inputs, null, 2)}
|
|
|
|
|
+ </pre>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {queryResultData.data && (
|
|
|
|
|
+ <Card size="small" title={t("aiOrderV2.fieldResult")}>
|
|
|
|
|
+ {queryResultData.data.images && queryResultData.data.images.length > 0 && (
|
|
|
|
|
+ <div className="mb-3">
|
|
|
|
|
+ <div className="text-sm text-gray-500 mb-2">{t("aiOrderV2.queryFieldImages")}</div>
|
|
|
|
|
+ <Image.PreviewGroup>
|
|
|
|
|
+ <div className="flex flex-wrap gap-2">
|
|
|
|
|
+ {queryResultData.data.images.map((url, idx) => (
|
|
|
|
|
+ <Image
|
|
|
|
|
+ key={`qimg-${idx}`}
|
|
|
|
|
+ src={url}
|
|
|
|
|
+ width={100}
|
|
|
|
|
+ height={100}
|
|
|
|
|
+ style={{ objectFit: "cover", borderRadius: 8 }}
|
|
|
|
|
+ />
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Image.PreviewGroup>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ {queryResultData.data.videos && queryResultData.data.videos.length > 0 && (
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div className="text-sm text-gray-500 mb-2">{t("aiOrderV2.queryFieldVideos")}</div>
|
|
|
|
|
+ <div className="flex flex-wrap gap-2">
|
|
|
|
|
+ {queryResultData.data.videos.map((url, idx) => (
|
|
|
|
|
+ <a
|
|
|
|
|
+ key={`qvid-${idx}`}
|
|
|
|
|
+ href={url}
|
|
|
|
|
+ target="_blank"
|
|
|
|
|
+ rel="noopener noreferrer"
|
|
|
|
|
+ className="group relative block shrink-0 overflow-hidden rounded-lg bg-black"
|
|
|
|
|
+ style={{ width: 100, height: 100 }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <video src={url} muted preload="metadata" className="h-full w-full object-cover" />
|
|
|
|
|
+ <div className="absolute inset-0 flex items-center justify-center bg-black/30 transition-colors group-hover:bg-black/40">
|
|
|
|
|
+ <PlayCircleOutlined className="text-white" style={{ fontSize: 28 }} />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ {(!queryResultData.data.images || queryResultData.data.images.length === 0) &&
|
|
|
|
|
+ (!queryResultData.data.videos || queryResultData.data.videos.length === 0) && (
|
|
|
|
|
+ <span className="text-gray-300">—</span>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <span className="text-gray-300">—</span>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Modal>
|
|
|
</div>
|
|
</div>
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|