feat(runtime): 即梦本机运行时 - 接入安全生成闭环与桌面本机渠道

Refs #225

Co-authored-by: zkhyww <127009345+zkhyww@users.noreply.github.com>
This commit is contained in:
ddCat
2026-08-15 12:45:29 +08:00
parent 8f2232feee
commit 57e1820f41
179 changed files with 49746 additions and 1897 deletions

View File

@@ -1,15 +1,16 @@
import { App, Button, Drawer, Form, Input, Modal, Select, Switch, Tooltip, Typography } from "antd";
import { LayoutGrid, List, Plus, RefreshCw, Search } from "lucide-react";
import { LayoutGrid, List, Plus, RefreshCw, Search, Trash2 } from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useSearchParams } from "react-router";
import { ListToolbar, PageHeader, PaginationBar, WorkspacePage } from "@/components/layout/workspace-page";
import { WorkspaceState } from "@/components/layout/workspace-state";
import { CONTENT_MODERATION_ERROR_CODE, generationErrorMessage, isContentModerationError } from "@/lib/generation-error";
import { formatTaskKind, operationOptions, statusLabel } from "@/lib/generation-task-display";
import { formatTaskKind, isGenerationTaskSubmissionUncertain, operationOptions, statusLabel } from "@/lib/generation-task-display";
import { modelCapabilityConfigFor } from "@/lib/model-capabilities";
import { cancelGenerationTask, createAgentSession, createGenerationTask, listGenerationTasks, listTaskLogs, queryFailedVideoProviderTask, queryGenerationTask, retryGenerationTask, type CreateTaskInput, type GenerationTask, type TaskLog } from "@/services/api/task-center";
import { cancelGenerationTask, createAgentSession, createGenerationTask, deleteGenerationTask, formatTaskLog, listGenerationTasks, listTaskLogs, queryFailedVideoProviderTask, queryGenerationTask, refreshGenerationTaskStatus, retryGenerationTask, type CreateTaskInput, type GenerationTask, type TaskLog } from "@/services/api/task-center";
import { localDreaminaCancellationCopy, localDreaminaDetachOutcome } from "@/services/local-dreamina-task-projection";
import { syncGenerationTaskToCanvasStore } from "@/lib/canvas/canvas-generation-task-sync";
import { useCanvasStore } from "@/stores/canvas/use-canvas-store";
import { resolveModelRequestConfig, useConfigStore, useEffectiveConfig } from "@/stores/use-config-store";
@@ -289,15 +290,23 @@ export default function TasksPage() {
}, [loadTasks]);
const runAction = async (id: string, action: "retry" | "cancel") => {
const currentTask = tasksRef.current.find((task) => task.id === id);
if (action === "retry" && currentTask && isGenerationTaskSubmissionUncertain(currentTask)) {
message.warning("提交结果尚未确认,不能自动重试;请先核对官方状态,避免重复生成。");
return;
}
setActingId(id);
try {
const next = action === "retry" ? await retryGenerationTask(id) : await cancelGenerationTask(id);
setTasks((items) => items.map((item) => (item.id === id ? next : item)));
setDetailTask((current) => (current?.id === id ? { ...current, ...next } : current));
if (action === "retry") {
setStatusFilter("active");
setPage(1);
}
const localOutcome = localDreaminaDetachOutcome(next);
if (action === "retry") message.success("任务已重新入队");
else if (localOutcome?.kind === "background") message.info(localOutcome.message);
else if (next.providerCancelStatus === "requested") message.info("已请求上游取消,正在确认费用状态");
else if (next.providerCancelStatus === "confirmed") message.success("上游已确认取消,积分已退回");
else if (next.providerCancelStatus === "uncertain") message.warning("任务已取消,上游费用待核对");
@@ -309,6 +318,48 @@ export default function TasksPage() {
}
};
const deleteLocalTask = (task: GenerationTask) => {
Modal.confirm({
title: "删除本机任务记录?",
content:
localDreaminaCancellationCopy(task)?.kind === "background"
? "任务已由官方接受;删除后仍会在后台同步官方状态。"
: task.status === "queued"
? "任务尚未提交官方;删除会取消本机排队且不会触发官方请求。"
: "这只会删除本机任务记录,不会删除已生成的素材。",
okText: "删除本机记录",
okButtonProps: { danger: true },
cancelText: "保留",
onOk: async () => {
setActingId(task.id);
try {
await deleteGenerationTask(task.id);
setTasks((items) => items.filter((item) => item.id !== task.id));
if (detailTask?.id === task.id) setDetailTask(null);
message.success("本机任务记录已删除");
} catch (error) {
message.error(error instanceof Error ? error.message : "删除失败");
} finally {
setActingId("");
}
},
});
};
const refreshLocalTaskStatus = async (task: GenerationTask) => {
setActingId(task.id);
try {
const next = await refreshGenerationTaskStatus(task.id);
setTasks((items) => items.map((item) => (item.id === task.id ? { ...item, ...next } : item)));
setDetailTask((current) => (current?.id === task.id ? { ...current, ...next } : current));
message.success(next.officialStatus ? `官方返回状态:${next.officialStatus}` : "状态已更新");
} catch (error) {
message.error(error instanceof Error ? error.message : "更新状态失败");
} finally {
setActingId("");
}
};
const queryProviderTask = async (task: GenerationTask) => {
setActingId(task.id);
try {
@@ -491,7 +542,25 @@ export default function TasksPage() {
{detailTask.providerCancelStatus ? <InfoItem label="上游取消" value={providerCancelStatusLabel(detailTask)} /> : null}
{detailTask.providerCancelRequestedAt ? <InfoItem label="请求取消时间" value={formatDate(detailTask.providerCancelRequestedAt)} /> : null}
</div>
{canQueryProviderTask(detailTask) ? <div className="flex justify-end"><Button icon={<RefreshCw className="size-4" />} loading={actingId === detailTask.id} onClick={() => void queryProviderTask(detailTask)}>手动查询任务</Button></div> : null}
{detailTask.provider === "dreamina-cli" ? <p className="text-xs leading-5 text-foreground/60">官方状态采用最终一致轮询;转入后台后仍会继续等待并同步官方状态。官方即梦 CLI 当前不支持可靠的官方取消。</p> : null}
<div className="flex flex-wrap justify-end gap-2">
{detailTask.provider === "dreamina-cli" && detailTask.receiptRecorded && detailTask.status === "running" ? (
<Button aria-label="更新官方状态" icon={<RefreshCw className="size-4" />} loading={actingId === detailTask.id} onClick={() => void refreshLocalTaskStatus(detailTask)}>
更新官方状态
</Button>
) : null}
{detailTask.provider === "dreamina-cli" && (detailTask.status === "queued" || detailTask.status === "running") ? (
<Button danger loading={actingId === detailTask.id} onClick={() => void runAction(detailTask.id, "cancel")}>
{localDreaminaCancellationCopy(detailTask)?.action || "取消任务"}
</Button>
) : null}
{detailTask.provider === "dreamina-cli" ? (
<Button danger aria-label="删除本机记录" icon={<Trash2 className="size-4" />} loading={actingId === detailTask.id} onClick={() => deleteLocalTask(detailTask)}>
删除本机记录
</Button>
) : null}
{canQueryProviderTask(detailTask) ? <Button icon={<RefreshCw className="size-4" />} loading={actingId === detailTask.id} onClick={() => void queryProviderTask(detailTask)}>手动查询任务</Button> : null}
</div>
{detailTask.error ? <pre className="task-detail-error max-h-28 overflow-auto whitespace-pre-wrap px-3 py-2 text-xs">{generationErrorMessage(detailTask.error)}</pre> : null}
<TaskResultMedia value={detailTask.resultJson} taskType={detailTask.type} />
<DetailBlock title="输入" value={detailLoading ? "详情加载中..." : formatTaskJson(detailTask.inputJson)} />
@@ -499,7 +568,7 @@ export default function TasksPage() {
<div>
<Typography.Text strong>日志</Typography.Text>
<div className="mt-2 max-h-60 overflow-auto rounded-lg bg-slate-950 p-3 text-xs text-slate-100">
{logsLoading ? "日志加载中..." : taskLogs.length ? taskLogs.map((log) => `[${new Date(log.createdAt).toLocaleString()}] ${log.level.toUpperCase()} ${log.message}${log.payload ? `\n${generationErrorMessage(log.payload)}` : ""}`).join("\n\n") : "暂无日志"}
{logsLoading ? "日志加载中..." : taskLogs.length ? taskLogs.map((log) => `[${new Date(log.createdAt).toLocaleString()}] ${log.level.toUpperCase()} ${formatTaskLog(log)}`).join("\n\n") : "暂无日志"}
</div>
</div>
</div>