fix: harden ruxa polling and constrain canvas toolbars
This commit is contained in:
parent
ed3f0fefb7
commit
7ef76ec8f4
@ -640,7 +640,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
|||||||
<div className="relative h-dvh w-full overflow-hidden overscroll-none bg-background">
|
<div className="relative h-dvh w-full overflow-hidden overscroll-none bg-background">
|
||||||
|
|
||||||
{/* 1. Canvas Layer (Full Screen) - 使用 key 强制在 id 变化时重新挂载 */}
|
{/* 1. Canvas Layer (Full Screen) - 使用 key 强制在 id 变化时重新挂载 */}
|
||||||
<div className="absolute inset-0 z-0" key={`workspace-${id}`}>
|
<div className="absolute inset-0 z-0" key={`workspace-${id}`} data-canvas-stage="true">
|
||||||
<CanvasWorkspace occlusionRight={panelOcclusionRight} />
|
<CanvasWorkspace occlusionRight={panelOcclusionRight} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -686,6 +686,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
|||||||
"absolute z-10 transition-all duration-300 ease-in-out",
|
"absolute z-10 transition-all duration-300 ease-in-out",
|
||||||
isPanelOpen ? "translate-x-0 opacity-100" : "translate-x-[120%] opacity-0 pointer-events-none"
|
isPanelOpen ? "translate-x-0 opacity-100" : "translate-x-[120%] opacity-0 pointer-events-none"
|
||||||
)}
|
)}
|
||||||
|
data-canvas-right-panel="true"
|
||||||
style={{
|
style={{
|
||||||
top: DOCKED_PANEL_INSET,
|
top: DOCKED_PANEL_INSET,
|
||||||
right: DOCKED_PANEL_INSET,
|
right: DOCKED_PANEL_INSET,
|
||||||
@ -720,7 +721,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
|||||||
data-canvas-floating-toolbar="true"
|
data-canvas-floating-toolbar="true"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-1 rounded-[24px] border bg-background/88 px-2.5 py-1.5 shadow-2xl backdrop-blur-md"
|
className="flex items-center gap-1 rounded-[24px] border bg-background px-2.5 py-1.5 shadow-2xl"
|
||||||
style={{ marginLeft: `${floatingToolbarOffset}px` }}
|
style={{ marginLeft: `${floatingToolbarOffset}px` }}
|
||||||
>
|
>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
|
|||||||
@ -874,7 +874,6 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
|
|
||||||
let unsafeBottomTop = window.innerHeight;
|
let unsafeBottomTop = window.innerHeight;
|
||||||
const bottomOverlaySelectors = [
|
const bottomOverlaySelectors = [
|
||||||
'[data-canvas-chat-input-anchor="true"]',
|
|
||||||
'[data-canvas-floating-toolbar="true"]',
|
'[data-canvas-floating-toolbar="true"]',
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -905,26 +904,50 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
}
|
}
|
||||||
top = Math.min(Math.max(top, viewportPadding), maxTop);
|
top = Math.min(Math.max(top, viewportPadding), maxTop);
|
||||||
|
|
||||||
const maxToolbarWidth = window.innerWidth - viewportPadding * 2;
|
const stageElement = document.querySelector('[data-canvas-stage="true"]') as HTMLElement | null;
|
||||||
|
const panelElement = document.querySelector('[data-canvas-right-panel="true"]') as HTMLElement | null;
|
||||||
|
|
||||||
|
let safeLeft = viewportPadding;
|
||||||
|
let safeRight = window.innerWidth - viewportPadding;
|
||||||
|
if (stageElement) {
|
||||||
|
const stageRect = stageElement.getBoundingClientRect();
|
||||||
|
if (stageRect.width > 0) {
|
||||||
|
safeLeft = Math.max(safeLeft, stageRect.left + viewportPadding);
|
||||||
|
safeRight = Math.min(safeRight, stageRect.right - viewportPadding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (panelElement) {
|
||||||
|
const panelRect = panelElement.getBoundingClientRect();
|
||||||
|
if (panelRect.width > 0 && panelRect.left < window.innerWidth) {
|
||||||
|
safeRight = Math.min(safeRight, panelRect.left - viewportPadding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (safeRight <= safeLeft + 8) {
|
||||||
|
safeLeft = viewportPadding;
|
||||||
|
safeRight = window.innerWidth - viewportPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxToolbarWidth = Math.max(180, safeRight - safeLeft);
|
||||||
const effectiveToolbarWidth = Math.min(toolbarWidth, maxToolbarWidth);
|
const effectiveToolbarWidth = Math.min(toolbarWidth, maxToolbarWidth);
|
||||||
const halfToolbar = effectiveToolbarWidth / 2;
|
const halfToolbar = effectiveToolbarWidth / 2;
|
||||||
|
|
||||||
let left = rect.left + rect.width / 2;
|
let left = rect.left + rect.width / 2;
|
||||||
if (effectiveToolbarWidth >= maxToolbarWidth - 1) {
|
if (effectiveToolbarWidth >= maxToolbarWidth - 1) {
|
||||||
left = window.innerWidth / 2;
|
left = (safeLeft + safeRight) / 2;
|
||||||
} else {
|
} else {
|
||||||
left = Math.min(
|
left = Math.min(
|
||||||
Math.max(left, viewportPadding + halfToolbar),
|
Math.max(left, safeLeft + halfToolbar),
|
||||||
window.innerWidth - viewportPadding - halfToolbar
|
safeRight - halfToolbar
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
top,
|
top,
|
||||||
left,
|
left,
|
||||||
|
maxWidth: maxToolbarWidth,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { top: 0, left: 0 };
|
return { top: 0, left: 0, maxWidth: 480 };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTextPanelPosition = () => {
|
const getTextPanelPosition = () => {
|
||||||
@ -2184,7 +2207,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
|||||||
transform: 'translateX(-50%) scale(0.8)',
|
transform: 'translateX(-50%) scale(0.8)',
|
||||||
transformOrigin: 'top center',
|
transformOrigin: 'top center',
|
||||||
pointerEvents: 'auto',
|
pointerEvents: 'auto',
|
||||||
maxWidth: 'calc(100vw - 24px)',
|
maxWidth: `${Math.max(180, Math.floor(toolbarPosition.maxWidth))}px`,
|
||||||
overflowX: 'auto',
|
overflowX: 'auto',
|
||||||
scrollbarWidth: 'none',
|
scrollbarWidth: 'none',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ const DEFAULT_SIZE = "1:1";
|
|||||||
const DEFAULT_RESOLUTION = "1k";
|
const DEFAULT_RESOLUTION = "1k";
|
||||||
const DEFAULT_OUTPUT_FORMAT = "png";
|
const DEFAULT_OUTPUT_FORMAT = "png";
|
||||||
const POLL_INTERVAL_MS = 2000;
|
const POLL_INTERVAL_MS = 2000;
|
||||||
const POLL_MAX_ATTEMPTS = 60;
|
const POLL_MAX_ATTEMPTS = 90;
|
||||||
|
|
||||||
function normalizeUrlForCompare(url: string) {
|
function normalizeUrlForCompare(url: string) {
|
||||||
try {
|
try {
|
||||||
@ -109,10 +109,24 @@ function extractStatus(payload: any): string | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function extractErrorMessage(payload: any): string {
|
function extractErrorMessage(payload: any): string {
|
||||||
|
let parsedResultJson: any = null;
|
||||||
|
if (typeof payload?.resultJson === "string" && payload.resultJson.trim()) {
|
||||||
|
try {
|
||||||
|
parsedResultJson = JSON.parse(payload.resultJson);
|
||||||
|
} catch {
|
||||||
|
parsedResultJson = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
payload?.failMsg ||
|
||||||
|
payload?.data?.failMsg ||
|
||||||
payload?.message ||
|
payload?.message ||
|
||||||
payload?.error ||
|
payload?.error ||
|
||||||
payload?.errorMessage ||
|
payload?.errorMessage ||
|
||||||
|
parsedResultJson?.failMsg ||
|
||||||
|
parsedResultJson?.error ||
|
||||||
|
payload?.resultJson ||
|
||||||
payload?.data?.message ||
|
payload?.data?.message ||
|
||||||
payload?.data?.error ||
|
payload?.data?.error ||
|
||||||
"RUXA 任务执行失败"
|
"RUXA 任务执行失败"
|
||||||
@ -319,7 +333,7 @@ async function waitForTaskResult(baseURL: string, apiKey: string, taskId: string
|
|||||||
return imageUrls;
|
return imageUrls;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status && ["failed", "error", "cancelled", "canceled"].includes(status)) {
|
if (status && ["failed", "fail", "error", "cancelled", "canceled"].includes(status)) {
|
||||||
throw new Error(extractErrorMessage(payload));
|
throw new Error(extractErrorMessage(payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,6 +415,16 @@ export function createRuxaImage({ apiKey, baseURL }: RuxaImageConfig) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createCode =
|
||||||
|
typeof createPayload?.code === "number"
|
||||||
|
? createPayload.code
|
||||||
|
: Number.isFinite(Number(createPayload?.code))
|
||||||
|
? Number(createPayload.code)
|
||||||
|
: null;
|
||||||
|
if (createCode !== null && createCode !== 200) {
|
||||||
|
throw new Error(extractErrorMessage(createPayload));
|
||||||
|
}
|
||||||
|
|
||||||
let outputUrls = filterSourceImages(collectImageUrls(createPayload), imageUrls);
|
let outputUrls = filterSourceImages(collectImageUrls(createPayload), imageUrls);
|
||||||
if (outputUrls.length === 0) {
|
if (outputUrls.length === 0) {
|
||||||
const taskId = extractTaskId(createPayload);
|
const taskId = extractTaskId(createPayload);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user