fix: preserve main behavior after Claw merge
This commit is contained in:
parent
1dd489100d
commit
731aa9725a
@ -192,6 +192,11 @@ import {
|
||||
getAgentWorkflowPageFeatureSettings,
|
||||
sanitizeAgentWorkflowSelections,
|
||||
} from "@/lib/agents/workflow-ui-config";
|
||||
import {
|
||||
loadCanvasChatPanelModule,
|
||||
preloadCanvasChatPanel,
|
||||
} from "@/components/canvas/canvas-chat-panel-loader";
|
||||
import { preloadCanvasChatData } from "@/lib/client/canvas-chat-preload";
|
||||
import { cn, fetcher } from "@/lib/utils";
|
||||
import {
|
||||
buildVideoWorkflowCanvasNode,
|
||||
@ -313,7 +318,7 @@ const CanvasWorkspace = dynamic(
|
||||
|
||||
const CanvasChatPanel = dynamic(
|
||||
() =>
|
||||
import("@/components/canvas/CanvasChatPanel").then(
|
||||
loadCanvasChatPanelModule().then(
|
||||
(module) => module.CanvasChatPanel
|
||||
),
|
||||
{
|
||||
@ -3350,6 +3355,15 @@ export default function CanvasEditorLegacyPage({
|
||||
};
|
||||
}, [checkCanvasAuthSession]);
|
||||
|
||||
useEffect(() => {
|
||||
preloadCanvasChatPanel();
|
||||
preloadCanvasChatData(id);
|
||||
|
||||
return scheduleCanvasIdleTask(() => {
|
||||
void import("@/components/canvas/CanvasFileListPanel");
|
||||
}, 700);
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
setAreSecondaryPanelsReady(true);
|
||||
|
||||
@ -1238,7 +1238,7 @@ function DetailResultLocalEditDialog({
|
||||
);
|
||||
if (unchangedRegion) {
|
||||
setActiveRegionId(unchangedRegion.id);
|
||||
toast.error(`区域 ${unchangedRegion.index} 的目标框大小变化不明显,请重新框选`);
|
||||
toast.error(`区域 ${unchangedRegion.index} 的目标框大小和位置均未变化`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -12534,9 +12534,10 @@ export function CanvasChatPanel({
|
||||
void mutateClawdHistory();
|
||||
hasHydratedHistoryRef.current = true;
|
||||
} catch (error) {
|
||||
const wasCanceled =
|
||||
canceledClawdMessageIdsRef.current.has(userMessageId) ||
|
||||
(error instanceof Error && error.name === "AbortError");
|
||||
const wasCanceled = shouldRestoreClawdComposerAfterFailure({
|
||||
explicitlyCanceled:
|
||||
canceledClawdMessageIdsRef.current.has(userMessageId),
|
||||
});
|
||||
if (wasCanceled) {
|
||||
const shouldRestoreRequest =
|
||||
pendingClawdRequestRestoresRef.current.get(requestChatId) ===
|
||||
|
||||
@ -97,6 +97,7 @@ import {
|
||||
createLocalEditRegionId,
|
||||
getLocalEditRegionsFromMetadata,
|
||||
getNormalizedRectOverlayStyle,
|
||||
hasScaleAdjustTargetChanged,
|
||||
isHttpImageUrl,
|
||||
LOCAL_EDIT_COLORS,
|
||||
LOCAL_EDIT_MAX_REFERENCE_IMAGES_PER_REGION,
|
||||
@ -219,6 +220,9 @@ import {
|
||||
} from "@/lib/ai-wash/config";
|
||||
import { normalizeOptionalHexColor } from "@/lib/canvas/text-edit-colors";
|
||||
import { isCanvasImageUploadPending } from "@/lib/canvas/local-upload-state";
|
||||
import { createCanvasCapabilityClientCommand } from "@/lib/canvas/capabilities/client-command";
|
||||
import { getCanvasCapabilityByClientAction } from "@/lib/canvas/capabilities/registry";
|
||||
import type { CanvasCapabilityClientAction } from "@/lib/canvas/capabilities/types";
|
||||
import { cn, fetcher } from "@/lib/utils";
|
||||
import type {
|
||||
CanvasTextEditPayloadItem,
|
||||
@ -2049,6 +2053,7 @@ export function ImageNode({
|
||||
const selectNode = useCanvasStore((state) => state.selectNode);
|
||||
const projectId = useCanvasStore((state) => state.projectId);
|
||||
const getCanvasState = useCanvasStore((state) => state.getCanvasState);
|
||||
const loadCanvas = useCanvasStore((state) => state.loadCanvas);
|
||||
const addPendingChatAttachment = useCanvasStore(
|
||||
(state) => state.addPendingChatAttachment
|
||||
);
|
||||
@ -4418,15 +4423,11 @@ export function ImageNode({
|
||||
toast.error(`请为区域 ${missingTargetRegion.index} 框选青色目标框`);
|
||||
return;
|
||||
}
|
||||
const unchangedRegion = validRegions.find((region) => {
|
||||
const regionScalePercent = deriveCanvasScalePercentFromRects(
|
||||
region.rect,
|
||||
region.targetRect
|
||||
);
|
||||
return Math.abs(regionScalePercent) < 1;
|
||||
});
|
||||
const unchangedRegion = validRegions.find(
|
||||
(region) => !hasScaleAdjustTargetChanged(region.rect, region.targetRect)
|
||||
);
|
||||
if (unchangedRegion) {
|
||||
toast.error(`请调整区域 ${unchangedRegion.index} 的青色目标框大小`);
|
||||
toast.error(`请调整区域 ${unchangedRegion.index} 的青色目标框大小或位置`);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@ -6658,8 +6659,58 @@ export function ImageNode({
|
||||
updateNode,
|
||||
]);
|
||||
|
||||
// 处理图片编辑操作 - 集成我们现有的 API
|
||||
const handleAction = async (action: string) => {
|
||||
const executeSharedToolbarCapability = async (
|
||||
action: "rmbg" | "watermark" | "enhance" | "extend" | "layer-split"
|
||||
) => {
|
||||
if (!projectId) {
|
||||
toast.error("画布还在加载,请稍后再试");
|
||||
return;
|
||||
}
|
||||
const capability = getCanvasCapabilityByClientAction(action);
|
||||
if (!capability) {
|
||||
toast.error("画布能力未注册");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCreatingDetachedResultNode(false);
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
const command = createCanvasCapabilityClientCommand({
|
||||
capabilityId: capability.id,
|
||||
nodeIds: [node.id],
|
||||
projectId,
|
||||
source: "toolbar",
|
||||
});
|
||||
const response = await fetch(`/api/canvas/${projectId}/capabilities`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(command),
|
||||
});
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok || payload?.success !== true) {
|
||||
throw new Error(payload?.error || `${capability.title}失败`);
|
||||
}
|
||||
|
||||
const projectResult = await canvasService.getProject(projectId);
|
||||
if (!projectResult.success || !projectResult.data?.canvasData) {
|
||||
throw new Error(projectResult.error || "结果已生成,但画布同步失败");
|
||||
}
|
||||
loadCanvas(projectResult.data.canvasData);
|
||||
toast.success(`${capability.title}完成`);
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error ? error.message : `${capability.title}失败`;
|
||||
toast.error(message);
|
||||
} finally {
|
||||
setIsCreatingDetachedResultNode(false);
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理图片编辑操作
|
||||
const handleAction = async (
|
||||
action: CanvasCapabilityClientAction | "scene-fusion" | "annotate"
|
||||
) => {
|
||||
if (!data.src) {
|
||||
toast.error("请先上传图片");
|
||||
return;
|
||||
@ -6762,34 +6813,7 @@ export function ImageNode({
|
||||
toast.info("图层拆分功能未开启");
|
||||
return;
|
||||
}
|
||||
setIsCreatingDetachedResultNode(true);
|
||||
setIsProcessing(true);
|
||||
try {
|
||||
const result = await imageGenerationService.splitImageLayers(data.src);
|
||||
const layers = result.data?.layers || [];
|
||||
|
||||
if (!result.success || layers.length === 0) {
|
||||
toast.error(result.error || "图层拆分失败");
|
||||
return;
|
||||
}
|
||||
|
||||
const addedLayerCount = handleLayerSplitComplete(
|
||||
layers.map((layer) => ({
|
||||
originalUrl: layer.originalUrl,
|
||||
title: layer.title,
|
||||
layerIndex: layer.layerIndex,
|
||||
totalLayers: layer.totalLayers,
|
||||
bounds: layer.bounds ?? null,
|
||||
}))
|
||||
);
|
||||
toast.success(`图层拆分完成,已新增 ${addedLayerCount} 个图层结果`);
|
||||
} catch (error) {
|
||||
console.error("图层拆分失败:", error);
|
||||
toast.error("图层拆分失败");
|
||||
} finally {
|
||||
setIsCreatingDetachedResultNode(false);
|
||||
setIsProcessing(false);
|
||||
}
|
||||
await executeSharedToolbarCapability("layer-split");
|
||||
return;
|
||||
}
|
||||
if (action === "box-cutout") {
|
||||
@ -6851,102 +6875,13 @@ export function ImageNode({
|
||||
return;
|
||||
}
|
||||
|
||||
const actionName = getActionName(action);
|
||||
|
||||
setImageAdjustPanelOpen(false);
|
||||
setCropPanelOpen(false);
|
||||
setCropInteraction(null);
|
||||
setFlipRotatePanelOpen(false);
|
||||
setAnglePanelOpen(false);
|
||||
setIsCreatingDetachedResultNode(true);
|
||||
setIsProcessing(true);
|
||||
// 先创建一个"处理中"状态的节点,显示在原图右侧
|
||||
const newNodeId = addNode({
|
||||
type: "image",
|
||||
position: {
|
||||
x: node.position.x + node.size.width + 20,
|
||||
y: node.position.y,
|
||||
},
|
||||
size: { width: node.size.width, height: node.size.height },
|
||||
data: {
|
||||
src: "", // 暂时没有图片
|
||||
status: "generating",
|
||||
prompt: `${actionName}处理中...`,
|
||||
title: `${displayTitle}-${actionName}`,
|
||||
originalSrc: data.src,
|
||||
},
|
||||
connections: [node.id],
|
||||
});
|
||||
|
||||
let result;
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case "rmbg":
|
||||
result = await imageGenerationService.removeBackground(data.src);
|
||||
break;
|
||||
case "watermark":
|
||||
result = await imageGenerationService.removeWatermark(data.src);
|
||||
break;
|
||||
case "enhance":
|
||||
result = await imageGenerationService.enhanceImage(data.src);
|
||||
break;
|
||||
case "extend":
|
||||
result = await imageGenerationService.extendImage(data.src);
|
||||
break;
|
||||
default:
|
||||
// 删除创建的节点
|
||||
if (newNodeId) deleteNode(newNodeId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result?.success && result.data?.url) {
|
||||
// 更新节点为完成状态
|
||||
if (newNodeId) {
|
||||
updateNode(newNodeId, {
|
||||
data: {
|
||||
src: result.data.url,
|
||||
status: "completed",
|
||||
prompt: `${actionName}结果`,
|
||||
title: `${displayTitle}-${actionName}`,
|
||||
originalSrc: data.src,
|
||||
},
|
||||
});
|
||||
}
|
||||
toast.success(`${actionName}处理完成`);
|
||||
} else {
|
||||
// 更新节点为失败状态
|
||||
if (newNodeId) {
|
||||
updateNode(newNodeId, {
|
||||
data: {
|
||||
src: "",
|
||||
status: "failed",
|
||||
prompt: `${actionName}失败`,
|
||||
title: `${displayTitle}-${actionName}-失败`,
|
||||
originalSrc: data.src,
|
||||
},
|
||||
});
|
||||
}
|
||||
toast.error(result?.error || "处理失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`${action} 处理失败:`, error);
|
||||
// 更新节点为失败状态
|
||||
if (newNodeId) {
|
||||
updateNode(newNodeId, {
|
||||
data: {
|
||||
src: "",
|
||||
status: "failed",
|
||||
prompt: `${actionName}失败`,
|
||||
title: `${displayTitle}-${actionName}-失败`,
|
||||
originalSrc: data.src,
|
||||
},
|
||||
});
|
||||
}
|
||||
toast.error("处理失败,请重试");
|
||||
} finally {
|
||||
setIsCreatingDetachedResultNode(false);
|
||||
setIsProcessing(false);
|
||||
if (
|
||||
action === "rmbg" ||
|
||||
action === "watermark" ||
|
||||
action === "enhance" ||
|
||||
action === "extend"
|
||||
) {
|
||||
await executeSharedToolbarCapability(action);
|
||||
}
|
||||
};
|
||||
|
||||
@ -6990,7 +6925,16 @@ export function ImageNode({
|
||||
const action = customEvent.detail?.action;
|
||||
|
||||
if (!targetNodeId || !action || targetNodeId !== node.id) return;
|
||||
void handleAction(action);
|
||||
if (action === "scene-fusion" || action === "annotate") {
|
||||
void handleAction(action);
|
||||
return;
|
||||
}
|
||||
const capability = getCanvasCapabilityByClientAction(
|
||||
action as CanvasCapabilityClientAction
|
||||
);
|
||||
if (capability) {
|
||||
void handleAction(capability.clientAction);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener(
|
||||
|
||||
@ -29,7 +29,7 @@ const skippedExtensions = new Set([
|
||||
const skippedFiles = new Set(["pnpm-lock.yaml"]);
|
||||
|
||||
const secretPatterns = [
|
||||
{ pattern: /sk-(?:proj-)?[A-Za-z0-9_-]{24,}/, reason: "OpenAI-style API key" },
|
||||
{ pattern: /\bsk-(?:proj-)?[A-Za-z0-9_-]{24,}/, reason: "OpenAI-style API key" },
|
||||
{ pattern: /gh[pousr]_[A-Za-z0-9_]{36,}/, reason: "GitHub token" },
|
||||
{ pattern: /AKIA[0-9A-Z]{16}/, reason: "AWS access key id" },
|
||||
{ pattern: /xox[baprs]-[A-Za-z0-9-]{24,}/, reason: "Slack token" },
|
||||
|
||||
@ -26,7 +26,11 @@ expectIncludes(testHelpersScript, "playwright test --config=playwright.helpers.c
|
||||
expectIncludes(qualityScript, "pnpm test:helpers", "helper tests in quality gate");
|
||||
|
||||
expectIncludes(helpersConfig, 'testDir: "./tests"', "tests directory");
|
||||
expectIncludes(helpersConfig, "testMatch: /routes\\/canvas-chat-helpers\\.test\\.ts/", "single pure helper test target");
|
||||
expectIncludes(
|
||||
helpersConfig,
|
||||
"/routes\\/canvas-chat-helpers\\.test\\.ts/",
|
||||
"canvas helper test target"
|
||||
);
|
||||
expectIncludes(helpersConfig, 'reporter: process.env.CI ? "github" : "list"', "fast non-html reporter");
|
||||
expectIncludes(helpersConfig, "timeout: 60 * 1000", "bounded helper test timeout");
|
||||
|
||||
|
||||
@ -11,8 +11,12 @@ test.describe("canvas overlay density", () => {
|
||||
const source = readSource("components/canvas/CanvasChatPanel.tsx");
|
||||
|
||||
expect(source).toContain(
|
||||
"max-h-[360px] w-[292px] max-w-[calc(100vw-24px)]"
|
||||
"max-h-[360px] max-w-[calc(100vw-24px)]"
|
||||
);
|
||||
expect(source).toMatch(
|
||||
/style=\{\{ width: `\$\{modelSelectorWidth\}px` \}\}/
|
||||
);
|
||||
expect(source).toContain("DEFAULT_CANVAS_MODEL_SELECTOR_WIDTH = 360");
|
||||
expect(source).toContain(
|
||||
"h-9 rounded-[var(--ui-radius-control)] px-2.5 text-[13px]"
|
||||
);
|
||||
|
||||
@ -37,7 +37,9 @@ test.describe("Claw request lifecycle", () => {
|
||||
|
||||
expect(source).not.toContain('request.signal.addEventListener("abort"');
|
||||
expect(source).not.toContain("cancelClawdRequest(cancelKey)");
|
||||
expect(source).toContain("registerActiveClawdRequest(cancelKey");
|
||||
expect(source).toMatch(
|
||||
/registerActiveClawdRequest\(\s*(?:params\.)?cancelKey/
|
||||
);
|
||||
});
|
||||
|
||||
test("does not classify every AbortError as an explicit stop action", () => {
|
||||
|
||||
@ -25,11 +25,11 @@ test.describe("HiCAD embedded startup", () => {
|
||||
expect(vendorScript).toContain(
|
||||
'const brandedFaviconUrl = "/api/favicon?surface=hicad"'
|
||||
);
|
||||
expect(vendorScript).toContain(
|
||||
'` <link rel="icon" href="${brandedFaviconUrl}" />`'
|
||||
expect(vendorScript).toMatch(
|
||||
/` {2}<link rel="icon" href="\$\{brandedFaviconUrl\}" \/>`/
|
||||
);
|
||||
expect(vendorScript).toContain(
|
||||
'` <link rel="apple-touch-icon" href="${brandedFaviconUrl}" />`'
|
||||
expect(vendorScript).toMatch(
|
||||
/` {2}<link rel="apple-touch-icon" href="\$\{brandedFaviconUrl\}" \/>`/
|
||||
);
|
||||
expect(vendorScript).toMatch(
|
||||
/manifest\.icons = \[\s*\{\s*src: brandedFaviconUrl,\s*sizes: "any",/
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@ -23,9 +19,7 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"strictNullChecks": true
|
||||
},
|
||||
@ -45,10 +39,7 @@
|
||||
".next-build-hicad-20260728-140320/types/**/*.ts",
|
||||
".next-build-hicad-20260728-140320/dev/types/**/*.ts",
|
||||
".next-candidate-home-banner-20260804-bg/types/**/*.ts",
|
||||
".next-candidate-home-banner-20260804-bg/dev/types/**/*.ts",
|
||||
".next-candidate-home-banner-20260804-bg/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"services/pascal-editor"
|
||||
]
|
||||
"exclude": ["node_modules", "services/pascal-editor"]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user