fix(canvas): 提示词优化器 - 隔离多实例浮层定位并修复路由预加载测试

This commit is contained in:
ddCat
2026-08-25 14:33:34 +08:00
parent 5b7f173102
commit 0cbe24a19f
4 changed files with 23 additions and 7 deletions

View File

@@ -7,7 +7,7 @@
"dev": "vite --host 0.0.0.0 --port 3000", "dev": "vite --host 0.0.0.0 --port 3000",
"build": "tsc --noEmit && vite build", "build": "tsc --noEmit && vite build",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "bun test test/api-request.test.ts test/video-api-contracts.test.ts test/image-api-contracts.test.ts test/channel-model-catalog.test.ts test/model-protocols.test.ts test/agent-prompt-cache.test.ts test/canvas-agent-context.test.ts test/canvas-batch-connection.test.ts test/canvas-character-reference.test.ts test/canvas-document.test.ts test/canvas-drawing-engine.test.ts test/canvas-emotion-edit-plan.test.ts test/canvas-extension-nodes.test.ts test/canvas-folder.test.ts test/canvas-generation-layout.test.ts test/canvas-image-batch-retry.test.ts test/canvas-leafer-viewport.test.ts test/canvas-local-runtime.test.ts test/canvas-model-policy.test.ts test/canvas-resource-mention-editor.test.ts test/canvas-resource-references.test.ts test/canvas-storyboard-assets.test.ts test/canvas-storyboard-references.test.ts test/canvas-node-config-reset.test.ts test/canvas-node-registry.test.ts test/canvas-video-regeneration.test.ts test/canvas-video-timeline-segments.test.ts test/create-canvas-handoff.test.ts test/create-library-button.test.ts test/creation-references.test.ts test/generation-asset-repository.node.test.ts test/generation-storage-consistency.test.ts test/generation-task-durability.node.test.ts test/generation-task-local.test.ts test/generation-task-materializer.test.ts test/task-media-fallback.test.ts test/user-scoped-generation-persistence.test.ts test/local-channel-permission.test.ts test/local-channel-runtime-projection.node.test.mjs test/local-channel-task-center.node.test.mjs test/local-channel-ui.node.test.mjs test/local-cli-settings.test.ts test/local-dreamina-cli.test.ts test/local-dreamina-generation.test.ts test/local-dreamina-model-catalog.test.ts test/local-runtime-bootstrap.test.ts test/local-runtime-connection.test.ts test/local-runtime.test.ts test/client-id.test.ts test/grok-image-prompt-limit.test.ts test/image-resolution-tiers.test.ts test/gemini-image.test.ts test/libtv-import.test.ts test/timeline-to-ffmpeg.test.ts test/canvas-video-segment-args.test.ts test/tapnow-import.test.ts && bun test test/local-dreamina-cross-runtime.test.ts", "test": "bun test test/api-request.test.ts test/video-api-contracts.test.ts test/image-api-contracts.test.ts test/channel-model-catalog.test.ts test/model-protocols.test.ts test/agent-prompt-cache.test.ts test/canvas-agent-context.test.ts test/canvas-batch-connection.test.ts test/canvas-character-reference.test.ts test/canvas-document.test.ts test/canvas-drawing-engine.test.ts test/canvas-emotion-edit-plan.test.ts test/canvas-extension-nodes.test.ts test/canvas-folder.test.ts test/canvas-generation-layout.test.ts test/canvas-image-batch-retry.test.ts test/canvas-leafer-viewport.test.ts test/canvas-local-runtime.test.ts test/canvas-model-policy.test.ts test/canvas-resource-mention-editor.test.ts test/canvas-resource-references.test.ts test/canvas-storyboard-assets.test.ts test/canvas-storyboard-references.test.ts test/canvas-node-config-reset.test.ts test/canvas-node-registry.test.ts test/canvas-video-regeneration.test.ts test/canvas-video-timeline-segments.test.ts test/create-canvas-handoff.test.ts test/create-library-button.test.ts test/creation-references.test.ts test/generation-asset-repository.node.test.ts test/generation-storage-consistency.test.ts test/generation-task-durability.node.test.ts test/generation-task-local.test.ts test/generation-task-materializer.test.ts test/task-media-fallback.test.ts test/user-scoped-generation-persistence.test.ts test/local-channel-permission.test.ts test/local-channel-runtime-projection.node.test.mjs test/local-channel-task-center.node.test.mjs test/local-channel-ui.node.test.mjs test/local-cli-settings.test.ts test/local-dreamina-cli.test.ts test/local-dreamina-generation.test.ts test/local-dreamina-model-catalog.test.ts test/local-runtime-bootstrap.test.ts test/local-runtime-connection.test.ts test/local-runtime.test.ts test/client-id.test.ts test/grok-image-prompt-limit.test.ts test/image-resolution-tiers.test.ts test/gemini-image.test.ts test/libtv-import.test.ts test/timeline-to-ffmpeg.test.ts test/canvas-video-segment-args.test.ts test/tapnow-import.test.ts test/prompt-optimizer-drawer.test.ts && bun test test/local-dreamina-cross-runtime.test.ts",
"start": "vite preview --host 0.0.0.0 --port 3000", "start": "vite preview --host 0.0.0.0 --port 3000",
"format": "prettier --write .", "format": "prettier --write .",
"format:check": "prettier --check ." "format:check": "prettier --check ."

View File

@@ -101,6 +101,7 @@ export function CanvasPromptOptimizerDrawer({ open, children, prompt, generation
const { message } = App.useApp(); const { message } = App.useApp();
const abortRef = useRef<AbortController | null>(null); const abortRef = useRef<AbortController | null>(null);
const chatRef = useRef<HTMLDivElement | null>(null); const chatRef = useRef<HTMLDivElement | null>(null);
const contentRef = useRef<HTMLDivElement | null>(null);
const [mode, setMode] = useState<PromptOptimizationMode>("expand"); const [mode, setMode] = useState<PromptOptimizationMode>("expand");
const [activeOptimizerModel, setActiveOptimizerModel] = useState(""); const [activeOptimizerModel, setActiveOptimizerModel] = useState("");
const [draftPrompt, setDraftPrompt] = useState(prompt); const [draftPrompt, setDraftPrompt] = useState(prompt);
@@ -176,7 +177,7 @@ export function CanvasPromptOptimizerDrawer({ open, children, prompt, generation
}, [open]); }, [open]);
const syncPanelPositionToViewport = useCallback(() => { const syncPanelPositionToViewport = useCallback(() => {
const popoverRoot = document.querySelector<HTMLElement>(".canvas-prompt-optimizer-popover"); const popoverRoot = contentRef.current?.closest<HTMLElement>(".canvas-prompt-optimizer-popover");
if (!popoverRoot) return; if (!popoverRoot) return;
const rect = popoverRoot.getBoundingClientRect(); const rect = popoverRoot.getBoundingClientRect();
@@ -381,7 +382,7 @@ export function CanvasPromptOptimizerDrawer({ open, children, prompt, generation
}; };
const nudgePanel = (deltaX: number, deltaY: number) => { const nudgePanel = (deltaX: number, deltaY: number) => {
const popoverRoot = document.querySelector<HTMLElement>(".canvas-prompt-optimizer-popover"); const popoverRoot = contentRef.current?.closest<HTMLElement>(".canvas-prompt-optimizer-popover");
if (!popoverRoot) { if (!popoverRoot) {
setPanelOffset((current) => ({ x: current.x + deltaX, y: current.y + deltaY })); setPanelOffset((current) => ({ x: current.x + deltaX, y: current.y + deltaY }));
return; return;
@@ -622,7 +623,7 @@ export function CanvasPromptOptimizerDrawer({ open, children, prompt, generation
} as CSSProperties; } as CSSProperties;
const content = ( const content = (
<div className={`canvas-prompt-optimizer-panel${panelInteracting ? " is-interacting" : ""}`} data-canvas-no-zoom> <div ref={contentRef} className={`canvas-prompt-optimizer-panel${panelInteracting ? " is-interacting" : ""}`} data-canvas-no-zoom>
<div <div
className="canvas-prompt-optimizer-header" className="canvas-prompt-optimizer-header"
onPointerDown={(event) => { onPointerDown={(event) => {

View File

@@ -0,0 +1,14 @@
import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const source = readFileSync(resolve(import.meta.dir, "../src/components/canvas/canvas-prompt-optimizer-drawer.tsx"), "utf8");
describe("prompt optimizer drawer instance isolation", () => {
test("resolves the popover from the current drawer content instead of the first global match", () => {
expect(source).toContain("const contentRef = useRef<HTMLDivElement | null>(null);");
expect(source).toContain('contentRef.current?.closest<HTMLElement>(".canvas-prompt-optimizer-popover")');
expect(source).toContain('<div ref={contentRef} className={`canvas-prompt-optimizer-panel');
expect(source).not.toContain('document.querySelector<HTMLElement>(".canvas-prompt-optimizer-popover")');
});
});

View File

@@ -19,13 +19,14 @@ describe("workspace route loading", () => {
test("preloads the reported workspace routes before navigation", () => { test("preloads the reported workspace routes before navigation", () => {
const modules = source("../src/lib/workspace-route-modules.ts"); const modules = source("../src/lib/workspace-route-modules.ts");
const navigation = source("../src/components/layout/app-top-nav.tsx"); const navigation = source("../src/components/layout/workspace-sidebar-nav.tsx");
for (const route of ["projects", "canvas", "assets", "wallet", "create"]) { for (const route of ["projects", "canvas", "assets", "wallet", "create"]) {
expect(modules).toContain(`${route}: () => import`); expect(modules).toContain(`${route}: () => import`);
} }
expect(navigation).toContain("onPointerEnter={() => preloadWorkspaceRoute(tool.slug)}"); expect(navigation).toContain("onPointerEnter={() => preloadWorkspaceRoute(linkTo)}");
expect(navigation).toContain("onFocus={() => preloadWorkspaceRoute(tool.slug)}"); expect(navigation).toContain("onPointerDown={() => preloadWorkspaceRoute(linkTo)}");
expect(navigation).toContain("onFocus={() => preloadWorkspaceRoute(linkTo)}");
}); });
test("uses a quiet workspace skeleton for initial hydration", () => { test("uses a quiet workspace skeleton for initial hydration", () => {