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">
|
||||
|
||||
{/* 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} />
|
||||
</div>
|
||||
|
||||
@ -686,6 +686,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
"absolute z-10 transition-all duration-300 ease-in-out",
|
||||
isPanelOpen ? "translate-x-0 opacity-100" : "translate-x-[120%] opacity-0 pointer-events-none"
|
||||
)}
|
||||
data-canvas-right-panel="true"
|
||||
style={{
|
||||
top: DOCKED_PANEL_INSET,
|
||||
right: DOCKED_PANEL_INSET,
|
||||
@ -720,7 +721,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
|
||||
data-canvas-floating-toolbar="true"
|
||||
>
|
||||
<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` }}
|
||||
>
|
||||
<Tooltip>
|
||||
|
||||
@ -874,7 +874,6 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
|
||||
let unsafeBottomTop = window.innerHeight;
|
||||
const bottomOverlaySelectors = [
|
||||
'[data-canvas-chat-input-anchor="true"]',
|
||||
'[data-canvas-floating-toolbar="true"]',
|
||||
];
|
||||
|
||||
@ -905,26 +904,50 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
}
|
||||
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 halfToolbar = effectiveToolbarWidth / 2;
|
||||
|
||||
let left = rect.left + rect.width / 2;
|
||||
if (effectiveToolbarWidth >= maxToolbarWidth - 1) {
|
||||
left = window.innerWidth / 2;
|
||||
left = (safeLeft + safeRight) / 2;
|
||||
} else {
|
||||
left = Math.min(
|
||||
Math.max(left, viewportPadding + halfToolbar),
|
||||
window.innerWidth - viewportPadding - halfToolbar
|
||||
Math.max(left, safeLeft + halfToolbar),
|
||||
safeRight - halfToolbar
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
top,
|
||||
left,
|
||||
maxWidth: maxToolbarWidth,
|
||||
};
|
||||
}
|
||||
return { top: 0, left: 0 };
|
||||
return { top: 0, left: 0, maxWidth: 480 };
|
||||
};
|
||||
|
||||
const getTextPanelPosition = () => {
|
||||
@ -2184,7 +2207,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
transform: 'translateX(-50%) scale(0.8)',
|
||||
transformOrigin: 'top center',
|
||||
pointerEvents: 'auto',
|
||||
maxWidth: 'calc(100vw - 24px)',
|
||||
maxWidth: `${Math.max(180, Math.floor(toolbarPosition.maxWidth))}px`,
|
||||
overflowX: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
}}
|
||||
|
||||
@ -24,7 +24,7 @@ const DEFAULT_SIZE = "1:1";
|
||||
const DEFAULT_RESOLUTION = "1k";
|
||||
const DEFAULT_OUTPUT_FORMAT = "png";
|
||||
const POLL_INTERVAL_MS = 2000;
|
||||
const POLL_MAX_ATTEMPTS = 60;
|
||||
const POLL_MAX_ATTEMPTS = 90;
|
||||
|
||||
function normalizeUrlForCompare(url: string) {
|
||||
try {
|
||||
@ -109,10 +109,24 @@ function extractStatus(payload: any): string | undefined {
|
||||
}
|
||||
|
||||
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 (
|
||||
payload?.failMsg ||
|
||||
payload?.data?.failMsg ||
|
||||
payload?.message ||
|
||||
payload?.error ||
|
||||
payload?.errorMessage ||
|
||||
parsedResultJson?.failMsg ||
|
||||
parsedResultJson?.error ||
|
||||
payload?.resultJson ||
|
||||
payload?.data?.message ||
|
||||
payload?.data?.error ||
|
||||
"RUXA 任务执行失败"
|
||||
@ -319,7 +333,7 @@ async function waitForTaskResult(baseURL: string, apiKey: string, taskId: string
|
||||
return imageUrls;
|
||||
}
|
||||
|
||||
if (status && ["failed", "error", "cancelled", "canceled"].includes(status)) {
|
||||
if (status && ["failed", "fail", "error", "cancelled", "canceled"].includes(status)) {
|
||||
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);
|
||||
if (outputUrls.length === 0) {
|
||||
const taskId = extractTaskId(createPayload);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user