refactor: remove "use client" directive from multiple files and update project structure to align with Vite and React Router

This commit is contained in:
HouYunFei
2026-06-26 17:24:48 +08:00
parent e635ec6908
commit 443afab7bd
88 changed files with 94 additions and 230 deletions
+4 -3
View File
@@ -32,15 +32,16 @@
## 前端规范
- 前端使用 Next.js App Router、React、TypeScript、Ant Design、Tailwind、Zustand。
- 前端使用 Vite、React、React Router、TypeScript、Ant Design、Tailwind、Zustand。
- 编写 Ant Design 相关代码时,参考 https://ant.design/llms-full.txt 理解组件 API、示例和设计规范,并优先结合项目当前 antd 版本与既有写法。
- API 请求统一放在 `web/src/services/api/`
- 全局或跨页面状态优先放在 `web/src/stores/`
- 已经放在全局 store 或全局 hook 中的状态/动作,组件需要时直接使用对应 store/hook,不要为了“纯组件”层层透传 props;避免一个组件传递过多参数。
- 全局组件、全局常量、全局配置等全局性质的内容不要作为 props 或参数层层传递;哪里需要就在哪里直接从对应全局入口获取。
- 多个页面重复出现的 UI 副作用动作,例如复制文本并提示、下载并提示、统一确认弹窗,优先抽成 `web/src/hooks/` 下的全局 hook;不要放进 store,除非它确实是需要共享/订阅的状态。
- 画布相关状态和组件放在 `web/src/app/(user)/canvas/` 内部
- 页面里只有一个主业务组件时直接写在 `page.tsx`,不要单独拆 `Manager` 组件再传一堆 props
- 路由页面放在 `web/src/pages/`,页面布局放在 `web/src/layouts/`,路由配置放在 `web/src/router.tsx`
- 画布页面放在 `web/src/pages/canvas/`,画布组件放在 `web/src/components/canvas/`,画布状态放在 `web/src/stores/canvas/`,画布工具函数放在 `web/src/lib/canvas/`
- 页面按目录组织,例如 `web/src/pages/image/index.tsx`;页面里只有一个主业务组件时直接写在对应页面入口中,不要单独拆 `Manager` 组件再传一堆 props。
- 不要新增只做简单转发的组件,例如只 `return <X>{children}</X>` 或只换个名字透传 props;直接在使用处使用真实组件或把逻辑写进当前文件。
- 页面私有 hook 放在对应页面目录下,例如 `admin/assets/use-admin-assets.ts`;只有多个页面真实复用的 hook 才放到外层 `hooks/`
- 管理后台页面私有组件放到各自页面目录的 `components/` 下,例如 `admin/assets/components/``admin/prompts/components/`;不要为了单页面使用放到 `admin/components/` 共享目录。
+1 -1
View File
@@ -11,4 +11,4 @@ description: 当前版本已实现但仍需人工验证的变更项
- 音频和视频生成:选择 Gemini 调用格式时先给出不支持提示,仍需使用 OpenAI 兼容渠道。
- WebDAV 同步:移除 Next.js 转发代理和连接方式选择,改为浏览器前端直接连接 WebDAV 服务。
- 提示词库:移除 Next.js route 抓取和服务端内存缓存,改为浏览器前端直连 GitHub 原始文件并缓存到 IndexedDB。
- 前端构建:移除 Next.js,改为 Vite + React Router,需验证首页、图片/视频工作台、素材、提示词库和画布路由跳转。
- 前端构建:移除 Next.js,改为 Vite + React Router并将前端目录调整为 `pages` / `components` / `stores` / `lib` / `layouts` / `styles`需验证首页、图片/视频工作台、素材、提示词库和画布路由跳转。
-5
View File
@@ -1,5 +0,0 @@
import CanvasClientPage from "./canvas-client-page";
export default function CanvasPage() {
return <CanvasClientPage />;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

@@ -1,5 +1,3 @@
"use client";
import { type ReactNode } from "react";
import { ImageSettingsTheme } from "@/components/image-settings-panel";
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useMemo, useState } from "react";
import { Empty, Input, Modal, Pagination, Tag } from "antd";
import { Search } from "lucide-react";
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useRef, useState, type ReactNode } from "react";
import { Button, Tooltip } from "antd";
import { ArrowUp, CheckCircle2, CircleAlert, ImagePlus, LoaderCircle, UserRound, Wrench, X, XCircle } from "lucide-react";
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useMemo, useRef, useState } from "react";
import copyToClipboard from "copy-to-clipboard";
import { Bot, Copy, Cpu, History, PanelRightClose, Plus, Settings2, Trash2, X } from "lucide-react";
@@ -20,10 +18,10 @@ import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/u
import { CanvasPromptLibrary } from "./canvas-prompt-library";
import { AgentChatComposer, AgentChatMessage, AgentModeSwitch, AgentPanelTabs, AgentWorkingMessage, type CanvasAgentChatMessage, type CanvasAgentMode } from "./canvas-agent-chat-ui";
import { CanvasLocalAgentPanel } from "./canvas-local-agent-panel";
import { NODE_DEFAULT_SIZE } from "../constants";
import { CanvasNodeType, type CanvasAssistantMessage, type CanvasAssistantReference, type CanvasAssistantSession, type CanvasNodeData } from "../types";
import { useCanvasAgentStore } from "../stores/use-canvas-agent-store";
import { summarizeCanvasAgentOps, type CanvasAgentOp, type CanvasAgentSnapshot } from "../utils/canvas-agent-ops";
import { NODE_DEFAULT_SIZE } from "@/constant/canvas";
import { CanvasNodeType, type CanvasAssistantMessage, type CanvasAssistantReference, type CanvasAssistantSession, type CanvasNodeData } from "@/types/canvas";
import { useCanvasAgentStore } from "@/stores/canvas/use-canvas-agent-store";
import { summarizeCanvasAgentOps, type CanvasAgentOp, type CanvasAgentSnapshot } from "@/lib/canvas/canvas-agent-ops";
export const CANVAS_AGENT_PANEL_MOTION_MS = 500;
const PANEL_MOTION_SECONDS = CANVAS_AGENT_PANEL_MOTION_MS / 1000;
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useRef, useState, type RefObject } from "react";
import { createPortal } from "react-dom";
import { Settings2 } from "lucide-react";
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useMemo, useRef, useState } from "react";
import type { CSSProperties, KeyboardEvent, MouseEvent, PointerEvent } from "react";
import { Button, Image } from "antd";
@@ -1,5 +1,3 @@
"use client";
import type { CSSProperties } from "react";
import { Image as ImageIcon, LoaderCircle, MessageSquare, Music2, Play, Settings2, Square, Video } from "lucide-react";
import { Button, Segmented } from "antd";
@@ -12,7 +10,7 @@ import { useThemeStore } from "@/stores/use-theme-store";
import { CanvasImageSettingsPopover } from "./canvas-image-settings-popover";
import { CanvasAudioSettingsPopover, type CanvasAudioSettingKey } from "./canvas-audio-settings-popover";
import { CanvasVideoSettingsPopover } from "./canvas-video-settings-popover";
import type { CanvasGenerationMode, CanvasNodeData, CanvasNodeMetadata } from "../types";
import type { CanvasGenerationMode, CanvasNodeData, CanvasNodeMetadata } from "@/types/canvas";
type CanvasConfigNodePanelProps = {
node: CanvasNodeData;
@@ -2,7 +2,7 @@ import type { MouseEvent as ReactMouseEvent } from "react";
import { canvasThemes } from "@/lib/canvas-theme";
import { useThemeStore } from "@/stores/use-theme-store";
import type { CanvasConnection, CanvasNodeData, ConnectionHandle, Position } from "../types";
import type { CanvasConnection, CanvasNodeData, ConnectionHandle, Position } from "@/types/canvas";
export function ConnectionPath({
connection,
@@ -1,12 +1,10 @@
"use client";
import { useEffect } from "react";
import type { ReactNode } from "react";
import { Plus, Trash2 } from "lucide-react";
import { canvasThemes } from "@/lib/canvas-theme";
import { useThemeStore } from "@/stores/use-theme-store";
import type { ContextMenuState } from "../types";
import type { ContextMenuState } from "@/types/canvas";
export function CanvasNodeContextMenu({ menu, onClose, onDuplicate, onDelete }: { menu: ContextMenuState; onClose: () => void; onDuplicate: () => void; onDelete: () => void }) {
const theme = canvasThemes[useThemeStore((state) => state.theme)];
@@ -1,10 +1,8 @@
"use client";
import { Button, Modal } from "antd";
import { useAssetStore } from "@/stores/use-asset-store";
import { useCanvasStore } from "../stores/use-canvas-store";
import { useCanvasUiStore } from "../stores/use-canvas-ui-store";
import { useCanvasStore } from "@/stores/canvas/use-canvas-store";
import { useCanvasUiStore } from "@/stores/canvas/use-canvas-ui-store";
export function CanvasDeleteProjectsDialog() {
const ids = useCanvasUiStore((state) => state.deleteProjectIds);
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useRef, useState, type RefObject } from "react";
import { createPortal } from "react-dom";
import { Settings2 } from "lucide-react";
@@ -1,5 +1,3 @@
"use client";
import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from "react";
import { Button, Card, Checkbox, Form, Modal, Space, Switch, Tag, Tooltip, Typography, theme as antdTheme } from "antd";
import { Ellipsis, Image as ImageIcon, Settings2 } from "lucide-react";
@@ -1,9 +1,7 @@
"use client";
import type { ReactNode } from "react";
import { Brush, Camera, Copy, FileText, Grid2x2, Lock, LockOpen, Maximize2, Scissors, Sparkles, Upload, ZoomIn } from "lucide-react";
import type { CanvasNodeData } from "../types";
import type { CanvasNodeData } from "@/types/canvas";
export type ImageNodeActionToolId = "copyPrompt" | "reversePrompt" | "replace" | "resize" | "maskEdit" | "crop" | "split" | "upscale" | "superResolve" | "angle" | "view";
export type ImageQuickToolId = "info" | "delete" | "saveAsset" | "download" | "edit" | ImageNodeActionToolId;
@@ -1,5 +1,3 @@
"use client";
import { useCallback, useEffect, useMemo, useRef, useState, type PointerEvent as ReactPointerEvent } from "react";
import { useSearchParams } from "react-router-dom";
import { App, Button, Input, Segmented, Tooltip } from "antd";
@@ -10,8 +8,8 @@ import { motion } from "motion/react";
import { canvasThemes } from "@/lib/canvas-theme";
import { useThemeStore } from "@/stores/use-theme-store";
import { useUserStore } from "@/stores/use-user-store";
import { useCanvasAgentStore, type AgentAttachment, type AgentChatItem, type AgentEventLog, type AgentPanelTab, type AgentPendingToolCall, type AgentThreadSummary } from "../stores/use-canvas-agent-store";
import { summarizeCanvasAgentOps, type CanvasAgentOp, type CanvasAgentSnapshot } from "../utils/canvas-agent-ops";
import { useCanvasAgentStore, type AgentAttachment, type AgentChatItem, type AgentEventLog, type AgentPanelTab, type AgentPendingToolCall, type AgentThreadSummary } from "@/stores/canvas/use-canvas-agent-store";
import { summarizeCanvasAgentOps, type CanvasAgentOp, type CanvasAgentSnapshot } from "@/lib/canvas/canvas-agent-ops";
import { AgentChatComposer, AgentChatMessage, AgentPanelTabs, AgentPendingToolCard, AgentWorkingMessage, type CanvasAgentChatAttachment } from "./canvas-agent-chat-ui";
const PANEL_MOTION_SECONDS = 0.5;
@@ -1,10 +1,8 @@
"use client";
import { useCallback, useMemo, useRef, useState } from "react";
import { canvasThemes } from "@/lib/canvas-theme";
import { useThemeStore } from "@/stores/use-theme-store";
import { CanvasNodeType, type CanvasNodeData, type ViewportTransform } from "../types";
import { CanvasNodeType, type CanvasNodeData, type ViewportTransform } from "@/types/canvas";
export function Minimap({ nodes, viewport, viewportSize, onViewportChange }: { nodes: CanvasNodeData[]; viewport: ViewportTransform; viewportSize: { width: number; height: number }; onViewportChange: (viewport: ViewportTransform) => void }) {
const theme = canvasThemes[useThemeStore((state) => state.theme)];
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useState } from "react";
import { Button, Modal, Segmented, Slider } from "antd";
import { RotateCcw, WandSparkles } from "lucide-react";
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useRef, useState, type PointerEvent as ReactPointerEvent } from "react";
import { Button, Modal } from "antd";
import { Check, Lock, LockOpen, X } from "lucide-react";
@@ -3,8 +3,8 @@ import { imageReferenceLabel } from "@/lib/image-reference-prompt";
import { seedanceReferenceLabel } from "@/lib/seedance-video";
import type { ReferenceImage } from "@/types/image";
import type { ReferenceAudio, ReferenceVideo } from "@/types/media";
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData } from "../types";
import { getGenerationResourceNodes } from "../utils/canvas-resource-references";
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData } from "@/types/canvas";
import { getGenerationResourceNodes } from "@/lib/canvas/canvas-resource-references";
export type NodeGenerationContext = {
prompt: string;
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useMemo, useState, type ReactNode } from "react";
import { App, Modal, Segmented, Tooltip } from "antd";
import { Download, Ellipsis, FolderPlus, Image as ImageIcon, Info, MessageSquare, Minus, Music2, Pencil, Plus, RefreshCw, Settings2, Trash2, Upload, Video } from "lucide-react";
@@ -8,7 +6,7 @@ import { canvasThemes } from "@/lib/canvas-theme";
import { formatBytes, getDataUrlByteSize } from "@/lib/image-utils";
import { useCopyText } from "@/hooks/use-copy-text";
import { useThemeStore } from "@/stores/use-theme-store";
import { CanvasNodeType, type CanvasNodeData, type ViewportTransform } from "../types";
import { CanvasNodeType, type CanvasNodeData, type ViewportTransform } from "@/types/canvas";
import { ImageToolSettingsModal, type ImageToolbarSettingsTool } from "./canvas-image-toolbar-settings-modal";
import { IMAGE_QUICK_TOOLS_STORAGE_KEY, buildImageToolbarTools, defaultImageQuickToolIds, readImageQuickToolsConfig, type ImageQuickToolId } from "./canvas-image-toolbar-tools";
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useRef, useState, type PointerEvent as ReactPointerEvent } from "react";
import { Button, Input, Modal, Slider } from "antd";
import { Brush, Eraser, RotateCcw, WandSparkles, X } from "lucide-react";
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useState } from "react";
import { ArrowUp, LoaderCircle, Square } from "lucide-react";
import { Button } from "antd";
@@ -14,8 +12,8 @@ import { CanvasPromptLibrary } from "./canvas-prompt-library";
import { CanvasAudioSettingsPopover, type CanvasAudioSettingKey } from "./canvas-audio-settings-popover";
import { CanvasResourceMentionTextarea } from "./canvas-resource-mention-textarea";
import { CanvasVideoSettingsPopover } from "./canvas-video-settings-popover";
import { CanvasNodeType, type CanvasGenerationMode, type CanvasNodeData } from "../types";
import type { CanvasResourceReference } from "../utils/canvas-resource-references";
import { CanvasNodeType, type CanvasGenerationMode, type CanvasNodeData } from "@/types/canvas";
import type { CanvasResourceReference } from "@/lib/canvas/canvas-resource-references";
export type CanvasNodeGenerationMode = CanvasGenerationMode;
@@ -1,11 +1,9 @@
"use client";
import { useEffect, useState } from "react";
import { Button, InputNumber, Modal } from "antd";
import { Grid2x2 } from "lucide-react";
import { readImageMeta } from "@/lib/image-utils";
import type { ImageSplitParams } from "../utils/canvas-image-data";
import type { ImageSplitParams } from "@/lib/canvas/canvas-image-data";
export type CanvasImageSplitParams = ImageSplitParams;
@@ -1,11 +1,9 @@
"use client";
import { useEffect, useMemo, useState } from "react";
import { Button, Modal, Segmented } from "antd";
import { ImagePlus } from "lucide-react";
import { readImageMeta } from "@/lib/image-utils";
import { MAX_UPSCALE_LONG_EDGE, resolveUpscaleSize, type ImageUpscaleAlgorithm, type ImageUpscaleParams } from "../utils/canvas-image-data";
import { MAX_UPSCALE_LONG_EDGE, resolveUpscaleSize, type ImageUpscaleAlgorithm, type ImageUpscaleParams } from "@/lib/canvas/canvas-image-data";
export type CanvasImageUpscaleParams = ImageUpscaleParams;
@@ -1,5 +1,3 @@
"use client";
import React, { useCallback, useEffect, useRef, useState } from "react";
import type { ReactNode } from "react";
import { ChevronRight, Image as ImageIcon, Music2, RefreshCw, Star, Video } from "lucide-react";
@@ -8,8 +6,8 @@ import { canvasThemes } from "@/lib/canvas-theme";
import { formatBytes } from "@/lib/image-utils";
import { useThemeStore } from "@/stores/use-theme-store";
import { CanvasResourceMentionTextarea } from "./canvas-resource-mention-textarea";
import { CanvasNodeType, type CanvasNodeData, type Position } from "../types";
import type { CanvasResourceReference } from "../utils/canvas-resource-references";
import { CanvasNodeType, type CanvasNodeData, type Position } from "@/types/canvas";
import type { CanvasResourceReference } from "@/lib/canvas/canvas-resource-references";
type ResizeCorner = "top-left" | "top-right" | "bottom-left" | "bottom-right";
const selectionBlue = "#2f80ff";
@@ -1,12 +1,10 @@
"use client";
import { Check, Download, Pencil, Trash2, X } from "lucide-react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { Button, Input } from "antd";
import { useCanvasStore, type CanvasProject } from "../stores/use-canvas-store";
import { useCanvasUiStore } from "../stores/use-canvas-ui-store";
import { exportCanvasProjects } from "../utils/canvas-export";
import { useCanvasStore, type CanvasProject } from "@/stores/canvas/use-canvas-store";
import { useCanvasUiStore } from "@/stores/canvas/use-canvas-ui-store";
import { exportCanvasProjects } from "@/lib/canvas/canvas-export";
export function CanvasProjectCard({ project }: { project: CanvasProject }) {
const navigate = useNavigate();
@@ -1,5 +1,3 @@
"use client";
import { useState } from "react";
import { Button, Tooltip } from "antd";
import { BookOpen } from "lucide-react";
@@ -1,5 +1,3 @@
"use client";
import { forwardRef, useMemo, useRef, useState } from "react";
import type { CSSProperties, MouseEvent, PointerEvent, TextareaHTMLAttributes } from "react";
import { createPortal } from "react-dom";
@@ -7,7 +5,7 @@ import { FileText, Image as ImageIcon, Music2, Video } from "lucide-react";
import { canvasThemes } from "@/lib/canvas-theme";
import { useThemeStore } from "@/stores/use-theme-store";
import type { CanvasResourceReference } from "../utils/canvas-resource-references";
import type { CanvasResourceReference } from "@/lib/canvas/canvas-resource-references";
type MentionState = {
start: number;
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { Select } from "antd";
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useRef, useState, type RefObject } from "react";
import { createPortal } from "react-dom";
import { Settings2 } from "lucide-react";
@@ -1,10 +1,8 @@
"use client";
import React, { useEffect, useRef, useState } from "react";
import { canvasThemes, type CanvasBackgroundMode } from "@/lib/canvas-theme";
import { useThemeStore } from "@/stores/use-theme-store";
import type { ViewportTransform } from "../types";
import type { ViewportTransform } from "@/types/canvas";
type InfiniteCanvasProps = {
containerRef: React.RefObject<HTMLDivElement | null>;
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useState } from "react";
import { LoaderCircle } from "lucide-react";
@@ -1,5 +1,3 @@
"use client";
import { type ReactNode, useState } from "react";
import { ConfigProvider, Switch } from "antd";
@@ -1,5 +1,3 @@
"use client";
import { App, Button, Form, Input, Modal, Progress, Select, Tabs } from "antd";
import { CircleAlert, Cloud, Plus, RefreshCw, Trash2, Wifi } from "lucide-react";
import { useState } from "react";
@@ -1,5 +1,3 @@
"use client";
import type { ReactNode } from "react";
import { useEffect } from "react";
import { ProConfigProvider } from "@ant-design/pro-components";
@@ -1,5 +1,3 @@
"use client";
import { Menu } from "lucide-react";
import { Link, useLocation } from "react-router-dom";
@@ -1,5 +1,3 @@
"use client";
import type { ReactNode } from "react";
import { useEffect, useRef } from "react";
import { App } from "antd";
@@ -1,5 +1,3 @@
"use client";
import { GithubOutlined } from "@ant-design/icons";
import { cn } from "@/lib/utils";
@@ -1,5 +1,3 @@
"use client";
import { Drawer } from "antd";
import { Link } from "react-router-dom";
@@ -1,5 +1,3 @@
"use client";
import type { CSSProperties } from "react";
import { BookOpen, Keyboard, Settings2 } from "lucide-react";
@@ -1,5 +1,3 @@
"use client";
import type { CSSProperties } from "react";
import { Modal, Tag, Timeline } from "antd";
import { useVersionCheck } from "@/hooks/use-version-check";
-2
View File
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useId, useMemo, useState } from "react";
import { Cpu } from "lucide-react";
@@ -1,5 +1,3 @@
"use client";
import { Copy } from "lucide-react";
import type { ReactNode } from "react";
import { Button, Card, Tag } from "antd";
@@ -1,5 +1,3 @@
"use client";
import { Check, Search } from "lucide-react";
import { type UIEvent, useEffect, useState } from "react";
import { App, Empty, Input, Modal, Spin, Tag } from "antd";
@@ -1,5 +1,3 @@
"use client";
import { useMemo } from "react";
import { useInfiniteQuery } from "@tanstack/react-query";
@@ -1,5 +1,3 @@
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import { Moon, Sun } from "lucide-react";
import { flushSync } from "react-dom";
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { animate, motion, useInView, useMotionValue, useReducedMotion, useTransform, type HTMLMotionProps } from "motion/react";
-2
View File
@@ -1,5 +1,3 @@
"use client"
import * as React from "react"
import { Select as SelectPrimitive } from "radix-ui"
@@ -1,5 +1,3 @@
"use client";
import { type ReactNode } from "react";
import { Switch } from "antd";
@@ -1,5 +1,5 @@
import { CanvasNodeType } from "./types";
import type { CanvasNodeMetadata } from "./types";
import { CanvasNodeType } from "@/types/canvas";
import type { CanvasNodeMetadata } from "@/types/canvas";
type CanvasNodeSpec = {
width: number;
-2
View File
@@ -1,5 +1,3 @@
"use client";
import { App } from "antd";
import copy from "copy-to-clipboard";
@@ -1,5 +1,3 @@
"use client";
import type { ReactNode } from "react";
import { AppTopNav } from "@/components/layout/app-top-nav";
@@ -1,7 +1,7 @@
import { nanoid } from "nanoid";
import { getNodeSpec } from "../constants";
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData, type CanvasNodeMetadata, type ViewportTransform } from "../types";
import { getNodeSpec } from "@/constant/canvas";
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData, type CanvasNodeMetadata, type ViewportTransform } from "@/types/canvas";
export type CanvasAgentOp =
| { type: "add_node"; id?: string; nodeType?: CanvasNodeType; title?: string; position?: { x: number; y: number }; x?: number; y?: number; width?: number; height?: number; metadata?: CanvasNodeMetadata }
@@ -3,8 +3,8 @@ import { saveAs } from "file-saver";
import { createZip } from "@/lib/zip";
import { getMediaBlob } from "@/services/file-storage";
import { getImageBlob } from "@/services/image-storage";
import type { CanvasExportAsset, CanvasExportFile } from "../export-types";
import type { CanvasProject } from "../stores/use-canvas-store";
import type { CanvasExportAsset, CanvasExportFile } from "@/types/canvas-export";
import type { CanvasProject } from "@/stores/canvas/use-canvas-store";
export async function exportCanvasProjects(projects: CanvasProject[], fileName = "无限画布") {
const zipFiles: { name: string; data: BlobPart }[] = [];
@@ -1,5 +1,3 @@
"use client";
export type ImageCropRect = {
x: number;
y: number;
@@ -1,5 +1,3 @@
"use client";
export function fitNodeSize(width: number, height: number, maxWidth = 640, maxHeight = 640) {
const w = Math.max(1, width);
const h = Math.max(1, height);
@@ -1,6 +1,6 @@
import { imageReferenceLabel } from "@/lib/image-reference-prompt";
import { seedanceReferenceLabel } from "@/lib/seedance-video";
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData } from "../types";
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData } from "@/types/canvas";
export type CanvasResourceKind = "image" | "video" | "audio" | "text";
+1 -1
View File
@@ -1,7 +1,7 @@
import React from "react";
import { createRoot } from "react-dom/client";
import "antd/dist/reset.css";
import "./app/globals.css";
import "./styles/globals.css";
import { RouterProvider } from "react-router-dom";
import { AppProviders } from "@/components/layout/app-providers";
@@ -1,5 +1,3 @@
"use client";
import { Copy, Download, PencilLine, Search, Trash2, Upload } from "lucide-react";
import { useEffect, useMemo, useRef, useState } from "react";
import { App, Button, Card, Drawer, Empty, Form, Image, Input, Modal, Pagination, Select, Space, Tag, Typography } from "antd";
@@ -1,5 +1,3 @@
"use client";
import { useEffect, useRef } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { App, Button } from "antd";
@@ -8,12 +6,12 @@ import { Download, FileUp, Plus } from "lucide-react";
import { readZip } from "@/lib/zip";
import { setMediaBlob } from "@/services/file-storage";
import { setImageBlob } from "@/services/image-storage";
import { CanvasDeleteProjectsDialog } from "./components/canvas-delete-projects-dialog";
import { CanvasProjectCard } from "./components/canvas-project-card";
import type { CanvasExportFile } from "./export-types";
import { useCanvasStore } from "./stores/use-canvas-store";
import { useCanvasUiStore } from "./stores/use-canvas-ui-store";
import { exportCanvasProjects } from "./utils/canvas-export";
import { CanvasDeleteProjectsDialog } from "@/components/canvas/canvas-delete-projects-dialog";
import { CanvasProjectCard } from "@/components/canvas/canvas-project-card";
import type { CanvasExportFile } from "@/types/canvas-export";
import { useCanvasStore } from "@/stores/canvas/use-canvas-store";
import { useCanvasUiStore } from "@/stores/canvas/use-canvas-ui-store";
import { exportCanvasProjects } from "@/lib/canvas/canvas-export";
export default function CanvasPage() {
const { message } = App.useApp();
@@ -1,5 +1,3 @@
"use client";
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import type { ChangeEvent as ReactChangeEvent, DragEvent as ReactDragEvent, MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent } from "react";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
@@ -19,35 +17,35 @@ import { canvasThemes, type CanvasBackgroundMode } from "@/lib/canvas-theme";
import { UserStatusActions } from "@/components/layout/user-status-actions";
import { useAssetStore } from "@/stores/use-asset-store";
import { useThemeStore } from "@/stores/use-theme-store";
import { cropDataUrl, splitDataUrl, upscaleDataUrl } from "../utils/canvas-image-data";
import { fitNodeSize, nodeSizeFromRatio } from "../utils/canvas-node-size";
import { cropDataUrl, splitDataUrl, upscaleDataUrl } from "@/lib/canvas/canvas-image-data";
import { fitNodeSize, nodeSizeFromRatio } from "@/lib/canvas/canvas-node-size";
import { App, Button, Dropdown, Modal } from "antd";
import { NODE_DEFAULT_SIZE, getNodeSpec } from "../constants";
import { ActiveConnectionPath, ConnectionPath } from "../components/canvas-connections";
import { CanvasConfigComposer } from "../components/canvas-config-composer";
import { CanvasConfigNodePanel } from "../components/canvas-config-node-panel";
import { CANVAS_AGENT_PANEL_MOTION_MS, CanvasAssistantPanel } from "../components/canvas-assistant-panel";
import { CanvasNodeContextMenu } from "../components/canvas-context-menu";
import { CanvasNodeAngleDialog, type CanvasImageAngleParams } from "../components/canvas-node-angle-dialog";
import { CanvasNodeCropDialog, type CanvasImageCropRect } from "../components/canvas-node-crop-dialog";
import { CanvasNodeMaskEditDialog, type CanvasImageMaskEditPayload } from "../components/canvas-node-mask-edit-dialog";
import { CanvasNodeSplitDialog, type CanvasImageSplitParams } from "../components/canvas-node-split-dialog";
import { CanvasNodeUpscaleDialog, type CanvasImageUpscaleParams } from "../components/canvas-node-upscale-dialog";
import { buildNodeGenerationContext, buildNodeGenerationInputs, buildNodeResponseMessages, hydrateNodeGenerationContext, type NodeGenerationInput } from "../components/canvas-node-generation";
import { CanvasNodeHoverToolbar, CanvasNodeInfoModal } from "../components/canvas-node-hover-toolbar";
import { InfiniteCanvas } from "../components/infinite-canvas";
import { Minimap } from "../components/canvas-mini-map";
import { CanvasNode } from "../components/canvas-node";
import { CanvasNodePromptPanel, type CanvasNodeGenerationMode } from "../components/canvas-node-prompt-panel";
import { CanvasToolbar } from "../components/canvas-toolbar";
import { AssetPickerModal, type InsertAssetPayload } from "../components/asset-picker-modal";
import { CanvasZoomControls } from "../components/canvas-zoom-controls";
import { CanvasLocalAgentPanel } from "../components/canvas-local-agent-panel";
import { useCanvasAgentStore } from "../stores/use-canvas-agent-store";
import { useCanvasStore } from "../stores/use-canvas-store";
import { applyCanvasAgentOps, type CanvasAgentOp, type CanvasAgentSnapshot } from "../utils/canvas-agent-ops";
import { buildCanvasResourceReferences, buildNodeMentionReferences } from "../utils/canvas-resource-references";
import type { CanvasAgentMode } from "../components/canvas-agent-chat-ui";
import { NODE_DEFAULT_SIZE, getNodeSpec } from "@/constant/canvas";
import { ActiveConnectionPath, ConnectionPath } from "@/components/canvas/canvas-connections";
import { CanvasConfigComposer } from "@/components/canvas/canvas-config-composer";
import { CanvasConfigNodePanel } from "@/components/canvas/canvas-config-node-panel";
import { CANVAS_AGENT_PANEL_MOTION_MS, CanvasAssistantPanel } from "@/components/canvas/canvas-assistant-panel";
import { CanvasNodeContextMenu } from "@/components/canvas/canvas-context-menu";
import { CanvasNodeAngleDialog, type CanvasImageAngleParams } from "@/components/canvas/canvas-node-angle-dialog";
import { CanvasNodeCropDialog, type CanvasImageCropRect } from "@/components/canvas/canvas-node-crop-dialog";
import { CanvasNodeMaskEditDialog, type CanvasImageMaskEditPayload } from "@/components/canvas/canvas-node-mask-edit-dialog";
import { CanvasNodeSplitDialog, type CanvasImageSplitParams } from "@/components/canvas/canvas-node-split-dialog";
import { CanvasNodeUpscaleDialog, type CanvasImageUpscaleParams } from "@/components/canvas/canvas-node-upscale-dialog";
import { buildNodeGenerationContext, buildNodeGenerationInputs, buildNodeResponseMessages, hydrateNodeGenerationContext, type NodeGenerationInput } from "@/components/canvas/canvas-node-generation";
import { CanvasNodeHoverToolbar, CanvasNodeInfoModal } from "@/components/canvas/canvas-node-hover-toolbar";
import { InfiniteCanvas } from "@/components/canvas/infinite-canvas";
import { Minimap } from "@/components/canvas/canvas-mini-map";
import { CanvasNode } from "@/components/canvas/canvas-node";
import { CanvasNodePromptPanel, type CanvasNodeGenerationMode } from "@/components/canvas/canvas-node-prompt-panel";
import { CanvasToolbar } from "@/components/canvas/canvas-toolbar";
import { AssetPickerModal, type InsertAssetPayload } from "@/components/canvas/asset-picker-modal";
import { CanvasZoomControls } from "@/components/canvas/canvas-zoom-controls";
import { CanvasLocalAgentPanel } from "@/components/canvas/canvas-local-agent-panel";
import { useCanvasAgentStore } from "@/stores/canvas/use-canvas-agent-store";
import { useCanvasStore } from "@/stores/canvas/use-canvas-store";
import { applyCanvasAgentOps, type CanvasAgentOp, type CanvasAgentSnapshot } from "@/lib/canvas/canvas-agent-ops";
import { buildCanvasResourceReferences, buildNodeMentionReferences } from "@/lib/canvas/canvas-resource-references";
import type { CanvasAgentMode } from "@/components/canvas/canvas-agent-chat-ui";
import {
CanvasNodeType,
type CanvasAssistantImage,
@@ -61,7 +59,7 @@ import {
type Position,
type SelectionBox,
type ViewportTransform,
} from "../types";
} from "@/types/canvas";
import type { ReferenceImage } from "@/types/image";
import type { ReferenceAudio } from "@/types/media";
@@ -1,5 +1,3 @@
"use client";
import { ArrowRight } from "lucide-react";
import { type ReactNode, useEffect, useState } from "react";
import { App, Button, Image, Tag } from "antd";
@@ -1,5 +1,3 @@
"use client";
import { ArrowLeft, ArrowRight, BookOpen, CheckSquare, ClipboardPaste, Download, FolderPlus, History, ImagePlus, LoaderCircle, PenLine, Plus, SlidersHorizontal, Sparkles, Trash2, Upload } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { App, Button, Checkbox, Drawer, Empty, Image, Input, Modal, Tag, Tooltip, Typography } from "antd";
@@ -9,7 +7,7 @@ import { saveAs } from "file-saver";
import { ImageSettingsPanel } from "@/components/image-settings-panel";
import { ModelPicker } from "@/components/model-picker";
import { PromptSelectDialog } from "@/components/prompts/prompt-select-dialog";
import { AssetPickerModal, type InsertAssetPayload } from "@/app/(user)/canvas/components/asset-picker-modal";
import { AssetPickerModal, type InsertAssetPayload } from "@/components/canvas/asset-picker-modal";
import { canvasThemes } from "@/lib/canvas-theme";
import { imageReferenceLabel } from "@/lib/image-reference-prompt";
import { modelOptionLabel, useConfigStore, useEffectiveConfig, type AiConfig } from "@/stores/use-config-store";
@@ -1,5 +1,3 @@
"use client";
import { Copy, FolderPlus } from "lucide-react";
import { Button, Modal, Space, Tag } from "antd";
@@ -1,12 +1,10 @@
"use client";
import { FolderPlus, Search } from "lucide-react";
import { type UIEvent, useEffect, useState } from "react";
import { App, Button, Empty, Input, Spin, Tag } from "antd";
import { PromptCard } from "@/components/prompts/prompt-card";
import { PromptDetailDialog } from "@/components/prompts/prompt-detail-dialog";
import { usePromptList } from "@/components/prompts/use-prompt-list";
import { PromptDetailDialog } from "./components/prompt-detail-dialog";
import { useCopyText } from "@/hooks/use-copy-text";
import { cn } from "@/lib/utils";
import { useAssetStore } from "@/stores/use-asset-store";
@@ -1,5 +1,3 @@
"use client";
import { ArrowLeft, ArrowRight, BookOpen, CheckSquare, ClipboardPaste, Download, FolderPlus, History, LoaderCircle, Music2, Plus, SlidersHorizontal, Sparkles, Trash2, Upload, VideoIcon } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { App, Button, Checkbox, Drawer, Empty, Input, Modal, Tag, Typography } from "antd";
@@ -7,7 +5,7 @@ import localforage from "localforage";
import { nanoid } from "nanoid";
import { saveAs } from "file-saver";
import { AssetPickerModal, type InsertAssetPayload } from "@/app/(user)/canvas/components/asset-picker-modal";
import { AssetPickerModal, type InsertAssetPayload } from "@/components/canvas/asset-picker-modal";
import { ModelPicker } from "@/components/model-picker";
import { PromptSelectDialog } from "@/components/prompts/prompt-select-dialog";
import { VideoSettingsPanel, normalizeVideoResolutionValue, normalizeVideoSizeValue, videoSizeLabel } from "@/components/video-settings-panel";
+10 -10
View File
@@ -1,14 +1,14 @@
import { createBrowserRouter, Outlet } from "react-router-dom";
import NotFound from "@/app/not-found";
import UserLayout from "@/app/(user)/layout";
import IndexPage from "@/app/(user)/page";
import ImagePage from "@/app/(user)/image/page";
import VideoPage from "@/app/(user)/video/page";
import AssetsPage from "@/app/(user)/assets/page";
import PromptsPage from "@/app/(user)/prompts/page";
import CanvasPage from "@/app/(user)/canvas/page";
import CanvasProjectPage from "@/app/(user)/canvas/[id]/page";
import UserLayout from "@/layouts/user-layout";
import AssetsPage from "@/pages/assets";
import CanvasPage from "@/pages/canvas";
import CanvasProjectPage from "@/pages/canvas/project";
import HomePage from "@/pages/home";
import ImagePage from "@/pages/image";
import NotFound from "@/pages/not-found";
import PromptsPage from "@/pages/prompts";
import VideoPage from "@/pages/video";
export const router = createBrowserRouter([
{
@@ -18,7 +18,7 @@ export const router = createBrowserRouter([
</UserLayout>
),
children: [
{ path: "/", element: <IndexPage /> },
{ path: "/", element: <HomePage /> },
{ path: "/image", element: <ImagePage /> },
{ path: "/video", element: <VideoPage /> },
{ path: "/assets", element: <AssetsPage /> },
-2
View File
@@ -1,5 +1,3 @@
"use client";
import localforage from "localforage";
export type Prompt = {
+2 -4
View File
@@ -1,5 +1,3 @@
"use client";
import localforage from "localforage";
import { getMediaBlob, resolveMediaUrl, setMediaBlob } from "@/services/file-storage";
@@ -8,8 +6,8 @@ import { downloadWebdavFile, uploadWebdavFile, WEBDAV_MANIFEST_FILE_NAME } from
import type { Asset } from "@/stores/use-asset-store";
import { useAssetStore } from "@/stores/use-asset-store";
import type { WebdavSyncConfig } from "@/stores/use-config-store";
import type { CanvasProject } from "@/app/(user)/canvas/stores/use-canvas-store";
import { useCanvasStore } from "@/app/(user)/canvas/stores/use-canvas-store";
import type { CanvasProject } from "@/stores/canvas/use-canvas-store";
import { useCanvasStore } from "@/stores/canvas/use-canvas-store";
type StoredLog = Record<string, unknown> & { id?: string };
export type AppSyncDomainKey = "canvas" | "assets" | "image-workbench" | "video-workbench";
-2
View File
@@ -1,5 +1,3 @@
"use client";
import localforage from "localforage";
import { nanoid } from "nanoid";
-2
View File
@@ -1,5 +1,3 @@
"use client";
import localforage from "localforage";
import { nanoid } from "nanoid";
-2
View File
@@ -1,5 +1,3 @@
"use client";
import type { WebdavSyncConfig } from "@/stores/use-config-store";
export const WEBDAV_MANIFEST_FILE_NAME = "manifest.json";
@@ -1,6 +1,6 @@
import { create } from "zustand";
import type { CanvasAgentOp } from "../utils/canvas-agent-ops";
import type { CanvasAgentOp } from "@/lib/canvas/canvas-agent-ops";
export type AgentChatRole = "user" | "assistant" | "system" | "tool" | "error";
export type AgentAttachment = { id: string; name: string; type: string; size: number; url: string; dataUrl: string };
@@ -4,7 +4,7 @@ import { persist, type PersistStorage, type StorageValue } from "zustand/middlew
import { nanoid } from "nanoid";
import { localForageStorage } from "@/lib/localforage-storage";
import type { CanvasBackgroundMode } from "@/lib/canvas-theme";
import type { CanvasAssistantSession, CanvasConnection, CanvasNodeData, ViewportTransform } from "../types";
import type { CanvasAssistantSession, CanvasConnection, CanvasNodeData, ViewportTransform } from "@/types/canvas";
export type CanvasProject = {
id: string;
+1 -3
View File
@@ -1,5 +1,3 @@
"use client";
import { create } from "zustand";
import { persist, type PersistStorage, type StorageValue } from "zustand/middleware";
@@ -89,7 +87,7 @@ export const useAssetStore = create<AssetStore>()(
replaceAssets: (assets) => set({ assets }),
cleanupImages: (extra) => {
window.setTimeout(async () => {
const { useCanvasStore } = await import("@/app/(user)/canvas/stores/use-canvas-store");
const { useCanvasStore } = await import("@/stores/canvas/use-canvas-store");
await cleanupUnusedImages({ assets: get().assets, projects: useCanvasStore.getState().projects, extra });
await cleanupUnusedMedia({ assets: get().assets, projects: useCanvasStore.getState().projects, extra });
}, 0);
-2
View File
@@ -1,5 +1,3 @@
"use client";
import { useMemo } from "react";
import { create } from "zustand";
import { persist } from "zustand/middleware";
-2
View File
@@ -1,5 +1,3 @@
"use client";
import { create } from "zustand";
export type LocalUser = {
@@ -1,4 +1,4 @@
import type { CanvasProject } from "./stores/use-canvas-store";
import type { CanvasProject } from "@/stores/canvas/use-canvas-store";
export type CanvasExportFile = {
app: "infinite-canvas";