feat(runtime): 即梦本机运行时 - 接入安全生成闭环与桌面本机渠道
Refs #225 Co-authored-by: zkhyww <127009345+zkhyww@users.noreply.github.com>
This commit is contained in:
1 parent
8f2232feee
commit
57e1820f41
179 files changed
+49746
-1897
No files matched your search
@@ -4,6 +4,9 @@ import { useQuery } from "@tanstack/react-query";
|
||||
import { useParams, useSearchParams } from "react-router";
|
||||
import { useConfigStore, useEffectiveConfig } from "@/stores/use-config-store";
|
||||
import { uploadMediaFile } from "@/services/file-storage";
|
||||
import { readLocalRuntimeBootstrapState } from "@/services/local-runtime-bootstrap";
|
||||
import { createCanvasGenerationLiveProjectAdapter, registerCanvasGenerationLiveProject } from "@/services/canvas-generation-consumer";
|
||||
import { getActiveUserScope } from "@/lib/user-scope";
|
||||
import { resourceFileUrl, resourceIdFromStorageKey } from "@/services/api/resources";
|
||||
import copyToClipboard from "copy-to-clipboard";
|
||||
import { nanoid } from "nanoid";
|
||||
@@ -11,7 +14,9 @@ import { canvasThemes, type CanvasBackgroundMode } from "@/lib/canvas-theme";
|
||||
import { persistCanvasMediaPerformanceMode, readCanvasMediaPerformanceMode } from "@/lib/canvas/canvas-performance-mode";
|
||||
import { summarizeCanvasContext } from "@/lib/canvas/canvas-context-summary";
|
||||
import { refreshCanvasCharacterReferenceNodes } from "@/lib/canvas/canvas-character-reference";
|
||||
import { shouldAutoConnectCanvasRuntime } from "@/lib/canvas/local-runtime-connection";
|
||||
import { useAssetStore } from "@/stores/use-asset-store";
|
||||
import { flushCanvasStorePersistence } from "@/stores/canvas/use-canvas-store";
|
||||
import { useThemeStore } from "@/stores/use-theme-store";
|
||||
import { useUserStore } from "@/stores/use-user-store";
|
||||
import { App } from "antd";
|
||||
@@ -60,6 +65,7 @@ import { CanvasConnectionCreateMenu, CanvasNodePanelOverlay } from "@/components
|
||||
import { CanvasLeaferGraphicsLayer } from "@/components/canvas/canvas-leafer-graphics-layer";
|
||||
import { CanvasFreeformEmptyState, CanvasLinkedProjectEmptyState, CanvasShortDramaEmptyState, CanvasShortDramaGuide, CanvasStoryInputNodeContent, CanvasStylePlaceholderNodeContent } from "@/components/canvas/canvas-short-drama-entry";
|
||||
import { createCanvasNode, getInputSummary, isHiddenBatchChild, persistCanvasWorkspaceMode, readCanvasWorkspaceMode } from "@/lib/canvas/canvas-project-domain";
|
||||
import { canvasAssetHandoffAttempt, finalizeCanvasAssetHandoff, uninsertedCanvasAssetHandoffPayloads } from "@/lib/canvas/canvas-asset-handoff";
|
||||
import { deriveStoryboardPipelineProgress } from "@/lib/canvas/canvas-storyboard-progress";
|
||||
import { CanvasAgentChangeToast, CanvasMergeStatusToast, CanvasUploadStatusToast } from "./canvas-project-feedback";
|
||||
import { backendProviderConfig, getGenerationCount } from "@/lib/canvas/canvas-project-generation";
|
||||
@@ -82,7 +88,7 @@ import { useCanvasStyleWorkflow } from "./use-canvas-style-workflow";
|
||||
import { useCanvasDirector } from "./use-canvas-director";
|
||||
import { useCanvasGeneration } from "./use-canvas-generation";
|
||||
import { useCanvasGenerationBatches } from "./use-canvas-generation-batches";
|
||||
import { useCanvasGenerationExecutor } from "./use-canvas-generation-executor";
|
||||
import { useCanvasGenerationExecutor, type CanvasNodeGenerationOptions } from "./use-canvas-generation-executor";
|
||||
import { useCanvasGenerationRetry } from "./use-canvas-generation-retry";
|
||||
import { useCanvasHistory } from "./use-canvas-history";
|
||||
import { useCanvasKeyboard } from "./use-canvas-keyboard";
|
||||
@@ -143,19 +149,22 @@ export default function CanvasPage() {
|
||||
function InfiniteCanvasPage() {
|
||||
const { message } = App.useApp();
|
||||
const params = useParams<{ id: string }>();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const projectId = params.id || "";
|
||||
const canvasStorageScope = getActiveUserScope();
|
||||
const localAgentConnected = useCanvasAgentStore((state) => state.connected);
|
||||
const localAgentActivity = useCanvasAgentStore((state) => state.activity);
|
||||
const localAgentEnabled = useCanvasAgentStore((state) => state.enabled);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const didInitialCenterRef = useRef(false);
|
||||
const toolbarHideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const assetHandoffRef = useRef("");
|
||||
|
||||
const config = useConfigStore((state) => state.config);
|
||||
const effectiveConfig = useEffectiveConfig();
|
||||
const isAiConfigReady = useConfigStore((state) => state.isAiConfigReady);
|
||||
const assets = useAssetStore((state) => state.assets);
|
||||
const assetsHydrated = useAssetStore((state) => state.hydrated);
|
||||
const cleanupAssetImages = useAssetStore((state) => state.cleanupImages);
|
||||
const theme = canvasThemes[useThemeStore((state) => state.theme)];
|
||||
const defaultDrawingEngine = useUserStore((state) => state.drawingEngine.defaultEngine);
|
||||
@@ -199,8 +208,8 @@ function InfiniteCanvasPage() {
|
||||
const [scriptScrollTopById, setScriptScrollTopById] = useState<Record<string, number>>({});
|
||||
const [directorNodeId, setDirectorNodeId] = useState<string | null>(null);
|
||||
const [versionCompareRootId, setVersionCompareRootId] = useState<string | null>(null);
|
||||
const codexAutoConnect = ["new", "recent", "choose"].includes(searchParams.get("mode") || "");
|
||||
const codexCompactAgent = codexAutoConnect && searchParams.has("agentUrl");
|
||||
const codexAutoConnect = shouldAutoConnectCanvasRuntime(searchParams);
|
||||
const codexCompactAgent = codexAutoConnect && readLocalRuntimeBootstrapState().legacyDeepLinkRejected;
|
||||
const [titleEditing, setTitleEditing] = useState(false);
|
||||
const [titleDraft, setTitleDraft] = useState("");
|
||||
const [shortcutRequestNonce, setShortcutRequestNonce] = useState(0);
|
||||
@@ -253,9 +262,20 @@ function InfiniteCanvasPage() {
|
||||
|
||||
const nodesRef = useRef(nodes);
|
||||
const connectionsRef = useRef(connections);
|
||||
const chatSessionsRef = useRef(chatSessions);
|
||||
const activeChatIdRef = useRef(activeChatId);
|
||||
const selectedNodeIdsRef = useRef(selectedNodeIds);
|
||||
const viewportRef = useRef(viewport);
|
||||
const generateNodeRef = useRef<((nodeId: string, mode: CanvasNodeGenerationMode, prompt: string) => Promise<void>) | null>(null);
|
||||
const generateNodeRef = useRef<((nodeId: string, mode: CanvasNodeGenerationMode, prompt: string, options?: CanvasNodeGenerationOptions) => Promise<void>) | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId) return;
|
||||
return registerCanvasGenerationLiveProject({
|
||||
scope: canvasStorageScope,
|
||||
projectId,
|
||||
adapter: createCanvasGenerationLiveProjectAdapter({ nodesRef, connectionsRef, chatSessionsRef, activeChatIdRef, setNodes, setConnections, setChatSessions, setActiveChatId }),
|
||||
});
|
||||
}, [canvasStorageScope, projectId]);
|
||||
|
||||
const { getHistoryCleanupContext, historyPausedRef, historyState, redoCanvas, resetHistory, undoCanvas } = useCanvasHistory({
|
||||
projectLoaded,
|
||||
@@ -318,17 +338,30 @@ function InfiniteCanvasPage() {
|
||||
}, [linkedProjectQuery.data, projectLoaded, setNodes]);
|
||||
const canvasContext = useMemo(() => summarizeCanvasContext(nodes, selectedNodeIds, linkedProjectQuery.data?.units), [linkedProjectQuery.data?.units, nodes, selectedNodeIds]);
|
||||
|
||||
const { bindGenerationTask, cancelNodeTask, confirmStopGeneration, finishGenerationRequest, openNodeTaskDetails, runningNodeId, setRunningNodeId, setTaskDetail, startGenerationRequest, taskDetail, taskDetailLoading, taskDetailLogs } =
|
||||
useCanvasGeneration({ projectId, domainProjectId: linkedProjectId, projectLoaded, nodes, nodesRef, setNodes });
|
||||
const {
|
||||
applyGenerationTaskResult,
|
||||
bindGenerationTask,
|
||||
cancelNodeTask,
|
||||
confirmStopGeneration,
|
||||
finishGenerationRequest,
|
||||
openNodeTaskDetails,
|
||||
runningNodeId,
|
||||
setRunningNodeId,
|
||||
setTaskDetail,
|
||||
startGenerationRequest,
|
||||
taskDetail,
|
||||
taskDetailLoading,
|
||||
taskDetailLogs,
|
||||
} = useCanvasGeneration({ projectId, domainProjectId: linkedProjectId, projectLoaded, nodes, nodesRef, setNodes });
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectLoaded || !["new", "recent", "choose"].includes(searchParams.get("mode") || "")) return;
|
||||
if (searchParams.has("agentUrl")) {
|
||||
if (!projectLoaded || !codexAutoConnect) return;
|
||||
if (codexCompactAgent) {
|
||||
setAgentMode("local");
|
||||
return;
|
||||
}
|
||||
openAgent("local");
|
||||
}, [openAgent, projectLoaded, searchParams, setAgentMode]);
|
||||
}, [codexAutoConnect, codexCompactAgent, openAgent, projectLoaded, setAgentMode]);
|
||||
|
||||
// 沉浸专注进入时收起智能体与小地图、重置 Dock 唤出态;仅响应「进入」瞬间,避免关闭专注内主动唤出的面板。
|
||||
const prevFocusModeRef = useRef(focusMode);
|
||||
@@ -348,9 +381,11 @@ function InfiniteCanvasPage() {
|
||||
useLayoutEffect(() => {
|
||||
nodesRef.current = nodes;
|
||||
connectionsRef.current = connections;
|
||||
chatSessionsRef.current = chatSessions;
|
||||
activeChatIdRef.current = activeChatId;
|
||||
selectedNodeIdsRef.current = selectedNodeIds;
|
||||
viewportRef.current = viewport;
|
||||
}, [nodes, connections, selectedNodeIds, viewport]);
|
||||
}, [activeChatId, chatSessions, nodes, connections, selectedNodeIds, viewport]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectLoaded) return;
|
||||
@@ -477,6 +512,42 @@ function InfiniteCanvasPage() {
|
||||
setDialogNodeId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectLoaded || !assetsHydrated || searchParams.get("mode") !== "handoff") return;
|
||||
const attempt = canvasAssetHandoffAttempt(assets, searchParams);
|
||||
const { assetIds, payloads } = attempt;
|
||||
if (!assetIds.length) return;
|
||||
const assetReadiness = assetIds
|
||||
.map((assetId) => {
|
||||
const asset = assets.find((candidate) => candidate.id === assetId);
|
||||
return `${assetId}:${asset?.kind || "missing"}`;
|
||||
})
|
||||
.join("|");
|
||||
const handoffKey = `${projectId}:${assetReadiness}`;
|
||||
if (assetHandoffRef.current === handoffKey) return;
|
||||
assetHandoffRef.current = handoffKey;
|
||||
|
||||
if (attempt.kind === "retry") return;
|
||||
const pendingPayloads = uninsertedCanvasAssetHandoffPayloads(nodesRef.current, payloads);
|
||||
const persistHandoff = async (createdNodes: CanvasNodeData[]) => {
|
||||
const finalized = await finalizeCanvasAssetHandoff({
|
||||
searchParams,
|
||||
currentNodes: nodesRef.current,
|
||||
createdNodes,
|
||||
persist: async (nextNodes) => {
|
||||
nodesRef.current = nextNodes;
|
||||
updateProject(projectId, { nodes: nextNodes });
|
||||
await flushCanvasStorePersistence();
|
||||
},
|
||||
});
|
||||
setSearchParams(finalized.searchParams, { replace: true });
|
||||
};
|
||||
const insertion = pendingPayloads.length ? handleProjectAssetsInsert(pendingPayloads) : Promise.resolve([] as CanvasNodeData[]);
|
||||
void insertion.then(persistHandoff).catch(() => {
|
||||
assetHandoffRef.current = "";
|
||||
});
|
||||
}, [assets, assetsHydrated, handleProjectAssetsInsert, message, nodesRef, projectId, projectLoaded, searchParams, setSearchParams, updateProject]);
|
||||
|
||||
// 时间线弹窗内新增素材的回填通道:素材库/上传创建节点后由弹窗通过 ref 加入草稿。
|
||||
const timelineAddNodeRef = useRef<((node: CanvasNodeData) => void) | null>(null);
|
||||
// 时间线作用域直连媒体入轨通道:素材库/项目资产/本地上传不落画布,仅加入时间线草稿。
|
||||
@@ -1063,6 +1134,8 @@ function InfiniteCanvasPage() {
|
||||
});
|
||||
|
||||
const handleAssistantSessionsChange = useCallback((sessions: CanvasAssistantSession[], activeId: string | null) => {
|
||||
chatSessionsRef.current = sessions;
|
||||
activeChatIdRef.current = activeId;
|
||||
setChatSessions(sessions);
|
||||
setActiveChatId(activeId);
|
||||
}, []);
|
||||
@@ -1193,6 +1266,7 @@ function InfiniteCanvasPage() {
|
||||
startGenerationRequest,
|
||||
finishGenerationRequest,
|
||||
bindGenerationTask,
|
||||
applyGenerationTaskResult,
|
||||
});
|
||||
useEffect(() => {
|
||||
generateNodeRef.current = handleGenerateNode;
|
||||
@@ -1230,6 +1304,7 @@ function InfiniteCanvasPage() {
|
||||
startGenerationRequest,
|
||||
finishGenerationRequest,
|
||||
bindGenerationTask,
|
||||
applyGenerationTaskResult,
|
||||
});
|
||||
|
||||
const generateImageFromTextNode = useCallback(
|
||||
|
||||
Reference in new issue
Block a user