chore(fork): 上游血缘重建 - 以 upstream/main@e70fe1b9 为基线重放本仓库定制(端口 7380/7381、AGENTS 约定、移除 backend/server)
Some checks failed
Publish container images / Quality checks (push) Has been cancelled
Publish container images / Resolve build version (push) Has been cancelled
Publish container images / Build open-ai-canvas-backend (amd64) (push) Has been cancelled
Publish container images / Build open-ai-canvas-backend (arm64) (push) Has been cancelled
Publish container images / Build open-ai-canvas-web (multi) (push) Has been cancelled
Publish container images / Build open-ai-canvas-yingce-agent (multi) (push) Has been cancelled
Publish container images / Publish open-ai-canvas-backend manifest (push) Has been cancelled
Publish container images / Publish open-ai-canvas-web manifest (push) Has been cancelled
Publish container images / Publish open-ai-canvas-yingce-agent manifest (push) Has been cancelled
Publish container images / Publish release assets (push) Has been cancelled

This commit is contained in:
2026-10-13 10:58:42 +08:00
parent e70fe1b9c9
commit d6d6eab512
24 changed files with 348 additions and 24 deletions

View File

@@ -4,14 +4,14 @@
"private": true,
"type": "module",
"scripts": {
"dev": "vite --host 0.0.0.0 --port 3000",
"dev": "vite --host 0.0.0.0 --port 7380",
"build": "tsc --noEmit && vite build",
"pretest": "bun test test/tiptap-dependency-versions.test.ts test/admin-ui-regressions.test.ts test/announcement-prompt-regressions.test.ts test/password-reset.test.ts test/oss-settings.test.ts test/channel-model-editor-form.test.ts test/channel-model-price-tier-form.test.ts test/admin-logical-models.test.ts && bun test test/canvas-grid-viewport.test.tsx",
"typecheck": "tsc --noEmit",
"test:canvas": "bun test test/canvas-media-performance.test.ts test/canvas-node-title-interaction.test.ts test/canvas-spatial-index.test.ts",
"test": "bun test",
"test:previs:e2e": "bun scripts/previs-p0-chrome-e2e.mjs",
"start": "vite preview --host 0.0.0.0 --port 3000",
"start": "vite preview --host 0.0.0.0 --port 7380",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint src test"

View File

@@ -396,6 +396,7 @@ export function CanvasNodePromptPanel({ projectId, node, isRunning, onPromptChan
<Button
type="text"
className={`canvas-node-composer-submit canvas-node-composer-submit-canvas ${showCost ? "has-cost" : ""}`}
data-canvas-panel-submit
disabled={isRunning || isSubmitDisabled}
style={
{

View File

@@ -219,6 +219,18 @@ export function CanvasViewport({
event.preventDefault();
interactingRef.current = true;
const current = viewportRef.current;
// 区域选择模式下滚轮用于上下浏览画布,缩放保留给 Ctrl/Meta + 滚轮(触控板捏合)。
if (boxSelectEnabled && !isPinchZoom) {
const shiftHorizontal = event.shiftKey && absX < 1;
scheduleViewportChange({
x: current.x - (shiftHorizontal ? deltaY : deltaX),
y: current.y - (shiftHorizontal ? 0 : deltaY),
k: current.k,
}, true);
return;
}
const rawAbsY = Math.abs(event.deltaY);
const looksLikeMouseWheel = event.deltaMode !== 0 || (rawAbsY >= 80 && Math.abs(rawAbsY - Math.round(rawAbsY / 100) * 100) < 1);
const looksLikeTrackpadPan = !isPinchZoom && (event.shiftKey || absX > 0 || (!looksLikeMouseWheel && absY > 0));
@@ -255,7 +267,7 @@ export function CanvasViewport({
true,
);
},
[containerRef, scheduleViewportChange],
[boxSelectEnabled, containerRef, scheduleViewportChange],
);
const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {

View File

@@ -1,5 +1,5 @@
import { motion, useReducedMotion } from "motion/react";
import { useLayoutEffect, useRef, useState, type CSSProperties, type ReactNode, type RefObject } from "react";
import { useLayoutEffect, useRef, useState, type CSSProperties, type KeyboardEvent as ReactKeyboardEvent, type ReactNode, type RefObject } from "react";
import { Clapperboard, Image as ImageIcon, List, Music2, Pencil, Table2, Video, WandSparkles, Workflow as WorkflowIcon } from "lucide-react";
import { useCanvasOverlayLayer } from "@/components/canvas/canvas-overlay-layer";
@@ -161,6 +161,19 @@ export function CanvasNodePanelOverlay({
};
}, [containerRef, dragOffset?.x, dragOffset?.y, isDragging, node.height, node.id, node.position.x, node.position.y, node.width, panelHeight, panelWidth, viewport]);
// 面板内任意位置按下 ⌘/Ctrl + Enter 时直接提交,避免先把手移回输入框或生成按钮。
// 只有面板自己声明了主按钮([data-canvas-panel-submit])才生效,且按钮禁用时不拦截按键。
const handleSubmitShortcut = (event: ReactKeyboardEvent<HTMLDivElement>) => {
if (event.key !== "Enter" || event.shiftKey || event.altKey) return;
if (!(event.ctrlKey || event.metaKey)) return;
if (event.nativeEvent.isComposing) return;
const submit = panelRef.current?.querySelector<HTMLButtonElement>("[data-canvas-panel-submit]");
if (!submit || submit.disabled || submit.getAttribute("aria-disabled") === "true") return;
event.preventDefault();
event.stopPropagation();
submit.click();
};
return (
<div
ref={panelRef}
@@ -168,6 +181,7 @@ export function CanvasNodePanelOverlay({
data-canvas-node-panel
className={`thin-scrollbar absolute max-w-[calc(100%_-_24px)] ${allowOverflow ? "overflow-visible" : "overflow-y-auto"}`}
style={{ left: 0, top: 0, transform: `translate3d(${initialPosition.left}px, ${initialPosition.top}px, 0)`, width: initialWidth, maxHeight: allowOverflow ? "none" : "calc(100% - 84px)", zIndex }}
onKeyDownCapture={handleSubmitShortcut}
onMouseDown={(event) => event.stopPropagation()}
onPointerDownCapture={bringToFront}
onFocusCapture={bringToFront}

View File

@@ -109,8 +109,8 @@ export const CANVAS_SHORTCUTS: CanvasShortcutItem[] = [
id: "box-select-tool",
category: "selection",
title: "使用区域选择工具",
description: "底部工具栏的胶囊开关切到区域选择后,空白处拖动可连续框选",
keys: [["区域选择", "拖动"]],
description: "底部工具栏的胶囊开关切到区域选择后,空白处拖动可连续框选;滚轮上下移动画布,缩放请用 Command/Ctrl + 滚轮",
keys: [["区域选择", "拖动"], ["区域选择", "滚轮"]],
keywords: ["工具栏", "多选", "selection"],
},
{

View File

@@ -1,6 +1,6 @@
import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react";
import { Link } from "react-router";
import { Clapperboard, CloudDownload, CopyPlus, Focus, FolderKanban, Home, LayoutGrid, LoaderCircle, Menu, Pencil, Plus, Redo2, Save, Search, Share2, Trash2, Undo2, Upload } from "lucide-react";
import { Clapperboard, CloudDownload, CopyPlus, Focus, FolderKanban, Home, LayoutGrid, LoaderCircle, Menu, Pencil, Plus, Redo2, Save, Search, Settings, Share2, Trash2, Undo2, Upload } from "lucide-react";
import { Button, Dropdown, Tooltip } from "antd";
import { WorkspaceCreditGiftMark } from "@/components/layout/workspace-credit-gift-mark";
@@ -10,6 +10,7 @@ import { canvasDockStyle } from "@/lib/canvas/canvas-aceternity-style";
import type { CanvasContextSummary } from "@/lib/canvas/canvas-context-summary";
import type { CanvasShortDramaProgress } from "@/lib/canvas/canvas-short-drama";
import { canvasThemes } from "@/lib/canvas-theme";
import { navigateToSettings } from "@/lib/settings-navigation";
import { useCanvasThemeStore } from "@/stores/canvas/use-canvas-theme-store";
import { useUserStore } from "@/stores/use-user-store";
import { CanvasShortcutsModal } from "./canvas-shortcuts-modal";
@@ -236,6 +237,9 @@ export function CanvasTopBar({
<CanvasTopBarTooltip label="分享画布">
<Button type="text" className="canvas-topbar-action !h-10 !w-10 !min-w-10 !rounded-xl !p-0" style={{ color: theme.node.text }} icon={<Share2 className="size-4" />} onClick={onShare} aria-label="分享画布" />
</CanvasTopBarTooltip>
<CanvasTopBarTooltip label="账户与设置">
<Button type="text" className="canvas-topbar-action !h-10 !w-10 !min-w-10 !rounded-xl !p-0" style={{ color: theme.node.text }} icon={<Settings className="size-4" />} onClick={() => navigateToSettings({ section: "channels", continueCreation: true })} aria-label="账户与设置" />
</CanvasTopBarTooltip>
</div>
</div>
<CanvasShortcutsModal open={shortcutsOpen} onClose={() => setShortcutsOpen(false)} />

View File

@@ -9,7 +9,9 @@ const appVersion = process.env.CANVAS_BUILD_VERSION?.trim() || readFileSync(reso
const buildCommit = process.env.CANVAS_BUILD_COMMIT?.trim() || process.env.VITE_BUILD_COMMIT?.trim() || "unknown";
const buildTime = process.env.CANVAS_BUILD_TIME?.trim() || process.env.VITE_BUILD_TIME?.trim() || "unknown";
const appChangelog = readFileSync(resolve(webDir, "../CHANGELOG.md"), "utf8");
const apiProxyTarget = process.env.VITE_API_PROXY_TARGET?.trim() || "http://127.0.0.1:8080";
// 本项目本地开发端口:前端 7380、后端 7381(见 AGENTS.md「本项目附加约定」)。
// 避开上游默认的 3000 / 8080,以便与 infinite-canvas、open-ai-canvas 等同机工程并存。
const apiProxyTarget = process.env.VITE_API_PROXY_TARGET?.trim() || "http://127.0.0.1:7381";
export default defineConfig({
plugins: [react()],