diff --git a/AGENTS.md b/AGENTS.md
index 803bca0..a5ce89d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -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 {children}` 或只换个名字透传 props;直接在使用处使用真实组件或把逻辑写进当前文件。
- 页面私有 hook 放在对应页面目录下,例如 `admin/assets/use-admin-assets.ts`;只有多个页面真实复用的 hook 才放到外层 `hooks/`。
- 管理后台页面私有组件放到各自页面目录的 `components/` 下,例如 `admin/assets/components/`、`admin/prompts/components/`;不要为了单页面使用放到 `admin/components/` 共享目录。
diff --git a/docs/content/docs/progress/pending-test.mdx b/docs/content/docs/progress/pending-test.mdx
index 3e6225a..69b9206 100644
--- a/docs/content/docs/progress/pending-test.mdx
+++ b/docs/content/docs/progress/pending-test.mdx
@@ -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`,需验证首页、图片/视频工作台、素材、提示词库和画布路由跳转。
diff --git a/web/src/app/(user)/canvas/[id]/page.tsx b/web/src/app/(user)/canvas/[id]/page.tsx
deleted file mode 100644
index 948e1e2..0000000
--- a/web/src/app/(user)/canvas/[id]/page.tsx
+++ /dev/null
@@ -1,5 +0,0 @@
-import CanvasClientPage from "./canvas-client-page";
-
-export default function CanvasPage() {
- return ;
-}
diff --git a/web/src/app/favicon.ico b/web/src/app/favicon.ico
deleted file mode 100644
index 35695ff..0000000
Binary files a/web/src/app/favicon.ico and /dev/null differ
diff --git a/web/src/components/audio-settings-panel.tsx b/web/src/components/audio-settings-panel.tsx
index 55a092c..d88e626 100644
--- a/web/src/components/audio-settings-panel.tsx
+++ b/web/src/components/audio-settings-panel.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { type ReactNode } from "react";
import { ImageSettingsTheme } from "@/components/image-settings-panel";
diff --git a/web/src/app/(user)/canvas/components/asset-picker-modal.tsx b/web/src/components/canvas/asset-picker-modal.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/asset-picker-modal.tsx
rename to web/src/components/canvas/asset-picker-modal.tsx
index 46a290f..e12462b 100644
--- a/web/src/app/(user)/canvas/components/asset-picker-modal.tsx
+++ b/web/src/components/canvas/asset-picker-modal.tsx
@@ -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";
diff --git a/web/src/app/(user)/canvas/components/canvas-agent-chat-ui.tsx b/web/src/components/canvas/canvas-agent-chat-ui.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-agent-chat-ui.tsx
rename to web/src/components/canvas/canvas-agent-chat-ui.tsx
index b227931..b8f622e 100644
--- a/web/src/app/(user)/canvas/components/canvas-agent-chat-ui.tsx
+++ b/web/src/components/canvas/canvas-agent-chat-ui.tsx
@@ -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";
diff --git a/web/src/app/(user)/canvas/components/canvas-assistant-panel.tsx b/web/src/components/canvas/canvas-assistant-panel.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-assistant-panel.tsx
rename to web/src/components/canvas/canvas-assistant-panel.tsx
index e30b5f6..41b1652 100644
--- a/web/src/app/(user)/canvas/components/canvas-assistant-panel.tsx
+++ b/web/src/components/canvas/canvas-assistant-panel.tsx
@@ -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;
diff --git a/web/src/app/(user)/canvas/components/canvas-audio-settings-popover.tsx b/web/src/components/canvas/canvas-audio-settings-popover.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-audio-settings-popover.tsx
rename to web/src/components/canvas/canvas-audio-settings-popover.tsx
index 6bdc7fa..2f20434 100644
--- a/web/src/app/(user)/canvas/components/canvas-audio-settings-popover.tsx
+++ b/web/src/components/canvas/canvas-audio-settings-popover.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { useEffect, useRef, useState, type RefObject } from "react";
import { createPortal } from "react-dom";
import { Settings2 } from "lucide-react";
diff --git a/web/src/app/(user)/canvas/components/canvas-config-composer.tsx b/web/src/components/canvas/canvas-config-composer.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-config-composer.tsx
rename to web/src/components/canvas/canvas-config-composer.tsx
index 1fbde49..679fd20 100644
--- a/web/src/app/(user)/canvas/components/canvas-config-composer.tsx
+++ b/web/src/components/canvas/canvas-config-composer.tsx
@@ -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";
diff --git a/web/src/app/(user)/canvas/components/canvas-config-node-panel.tsx b/web/src/components/canvas/canvas-config-node-panel.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-config-node-panel.tsx
rename to web/src/components/canvas/canvas-config-node-panel.tsx
index 8ac921e..54b4023 100644
--- a/web/src/app/(user)/canvas/components/canvas-config-node-panel.tsx
+++ b/web/src/components/canvas/canvas-config-node-panel.tsx
@@ -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;
diff --git a/web/src/app/(user)/canvas/components/canvas-connections.tsx b/web/src/components/canvas/canvas-connections.tsx
similarity index 98%
rename from web/src/app/(user)/canvas/components/canvas-connections.tsx
rename to web/src/components/canvas/canvas-connections.tsx
index 499f3a6..95cbb7a 100644
--- a/web/src/app/(user)/canvas/components/canvas-connections.tsx
+++ b/web/src/components/canvas/canvas-connections.tsx
@@ -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,
diff --git a/web/src/app/(user)/canvas/components/canvas-context-menu.tsx b/web/src/components/canvas/canvas-context-menu.tsx
similarity index 96%
rename from web/src/app/(user)/canvas/components/canvas-context-menu.tsx
rename to web/src/components/canvas/canvas-context-menu.tsx
index 5a1a034..18ac9ce 100644
--- a/web/src/app/(user)/canvas/components/canvas-context-menu.tsx
+++ b/web/src/components/canvas/canvas-context-menu.tsx
@@ -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)];
diff --git a/web/src/app/(user)/canvas/components/canvas-delete-projects-dialog.tsx b/web/src/components/canvas/canvas-delete-projects-dialog.tsx
similarity index 90%
rename from web/src/app/(user)/canvas/components/canvas-delete-projects-dialog.tsx
rename to web/src/components/canvas/canvas-delete-projects-dialog.tsx
index 6fc42c0..9b0f5f0 100644
--- a/web/src/app/(user)/canvas/components/canvas-delete-projects-dialog.tsx
+++ b/web/src/components/canvas/canvas-delete-projects-dialog.tsx
@@ -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);
diff --git a/web/src/app/(user)/canvas/components/canvas-image-settings-popover.tsx b/web/src/components/canvas/canvas-image-settings-popover.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-image-settings-popover.tsx
rename to web/src/components/canvas/canvas-image-settings-popover.tsx
index d636856..622f46d 100644
--- a/web/src/app/(user)/canvas/components/canvas-image-settings-popover.tsx
+++ b/web/src/components/canvas/canvas-image-settings-popover.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { useEffect, useRef, useState, type RefObject } from "react";
import { createPortal } from "react-dom";
import { Settings2 } from "lucide-react";
diff --git a/web/src/app/(user)/canvas/components/canvas-image-toolbar-settings-modal.tsx b/web/src/components/canvas/canvas-image-toolbar-settings-modal.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-image-toolbar-settings-modal.tsx
rename to web/src/components/canvas/canvas-image-toolbar-settings-modal.tsx
index 8fb1026..ded4517 100644
--- a/web/src/app/(user)/canvas/components/canvas-image-toolbar-settings-modal.tsx
+++ b/web/src/components/canvas/canvas-image-toolbar-settings-modal.tsx
@@ -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";
diff --git a/web/src/app/(user)/canvas/components/canvas-image-toolbar-tools.tsx b/web/src/components/canvas/canvas-image-toolbar-tools.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-image-toolbar-tools.tsx
rename to web/src/components/canvas/canvas-image-toolbar-tools.tsx
index a41cd1d..e8e0164 100644
--- a/web/src/app/(user)/canvas/components/canvas-image-toolbar-tools.tsx
+++ b/web/src/components/canvas/canvas-image-toolbar-tools.tsx
@@ -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;
diff --git a/web/src/app/(user)/canvas/components/canvas-local-agent-panel.tsx b/web/src/components/canvas/canvas-local-agent-panel.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-local-agent-panel.tsx
rename to web/src/components/canvas/canvas-local-agent-panel.tsx
index 99992ac..508c505 100644
--- a/web/src/app/(user)/canvas/components/canvas-local-agent-panel.tsx
+++ b/web/src/components/canvas/canvas-local-agent-panel.tsx
@@ -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;
diff --git a/web/src/app/(user)/canvas/components/canvas-mini-map.tsx b/web/src/components/canvas/canvas-mini-map.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-mini-map.tsx
rename to web/src/components/canvas/canvas-mini-map.tsx
index 1bf04f1..9433105 100644
--- a/web/src/app/(user)/canvas/components/canvas-mini-map.tsx
+++ b/web/src/components/canvas/canvas-mini-map.tsx
@@ -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)];
diff --git a/web/src/app/(user)/canvas/components/canvas-node-angle-dialog.tsx b/web/src/components/canvas/canvas-node-angle-dialog.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-node-angle-dialog.tsx
rename to web/src/components/canvas/canvas-node-angle-dialog.tsx
index c4514a3..2116c61 100644
--- a/web/src/app/(user)/canvas/components/canvas-node-angle-dialog.tsx
+++ b/web/src/components/canvas/canvas-node-angle-dialog.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { useEffect, useState } from "react";
import { Button, Modal, Segmented, Slider } from "antd";
import { RotateCcw, WandSparkles } from "lucide-react";
diff --git a/web/src/app/(user)/canvas/components/canvas-node-crop-dialog.tsx b/web/src/components/canvas/canvas-node-crop-dialog.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-node-crop-dialog.tsx
rename to web/src/components/canvas/canvas-node-crop-dialog.tsx
index fafbdbc..8f42834 100644
--- a/web/src/app/(user)/canvas/components/canvas-node-crop-dialog.tsx
+++ b/web/src/components/canvas/canvas-node-crop-dialog.tsx
@@ -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";
diff --git a/web/src/app/(user)/canvas/components/canvas-node-generation.ts b/web/src/components/canvas/canvas-node-generation.ts
similarity index 98%
rename from web/src/app/(user)/canvas/components/canvas-node-generation.ts
rename to web/src/components/canvas/canvas-node-generation.ts
index 3fd8ecf..5dd158f 100644
--- a/web/src/app/(user)/canvas/components/canvas-node-generation.ts
+++ b/web/src/components/canvas/canvas-node-generation.ts
@@ -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;
diff --git a/web/src/app/(user)/canvas/components/canvas-node-hover-toolbar.tsx b/web/src/components/canvas/canvas-node-hover-toolbar.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-node-hover-toolbar.tsx
rename to web/src/components/canvas/canvas-node-hover-toolbar.tsx
index 338423a..6a79801 100644
--- a/web/src/app/(user)/canvas/components/canvas-node-hover-toolbar.tsx
+++ b/web/src/components/canvas/canvas-node-hover-toolbar.tsx
@@ -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";
diff --git a/web/src/app/(user)/canvas/components/canvas-node-mask-edit-dialog.tsx b/web/src/components/canvas/canvas-node-mask-edit-dialog.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-node-mask-edit-dialog.tsx
rename to web/src/components/canvas/canvas-node-mask-edit-dialog.tsx
index 7367d93..6d413ce 100644
--- a/web/src/app/(user)/canvas/components/canvas-node-mask-edit-dialog.tsx
+++ b/web/src/components/canvas/canvas-node-mask-edit-dialog.tsx
@@ -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";
diff --git a/web/src/app/(user)/canvas/components/canvas-node-prompt-panel.tsx b/web/src/components/canvas/canvas-node-prompt-panel.tsx
similarity index 98%
rename from web/src/app/(user)/canvas/components/canvas-node-prompt-panel.tsx
rename to web/src/components/canvas/canvas-node-prompt-panel.tsx
index 592e547..7ce22ca 100644
--- a/web/src/app/(user)/canvas/components/canvas-node-prompt-panel.tsx
+++ b/web/src/components/canvas/canvas-node-prompt-panel.tsx
@@ -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;
diff --git a/web/src/app/(user)/canvas/components/canvas-node-split-dialog.tsx b/web/src/components/canvas/canvas-node-split-dialog.tsx
similarity index 98%
rename from web/src/app/(user)/canvas/components/canvas-node-split-dialog.tsx
rename to web/src/components/canvas/canvas-node-split-dialog.tsx
index ff05a55..a2f5a16 100644
--- a/web/src/app/(user)/canvas/components/canvas-node-split-dialog.tsx
+++ b/web/src/components/canvas/canvas-node-split-dialog.tsx
@@ -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;
diff --git a/web/src/app/(user)/canvas/components/canvas-node-upscale-dialog.tsx b/web/src/components/canvas/canvas-node-upscale-dialog.tsx
similarity index 98%
rename from web/src/app/(user)/canvas/components/canvas-node-upscale-dialog.tsx
rename to web/src/components/canvas/canvas-node-upscale-dialog.tsx
index cc00a5e..99f3e72 100644
--- a/web/src/app/(user)/canvas/components/canvas-node-upscale-dialog.tsx
+++ b/web/src/components/canvas/canvas-node-upscale-dialog.tsx
@@ -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;
diff --git a/web/src/app/(user)/canvas/components/canvas-node.tsx b/web/src/components/canvas/canvas-node.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-node.tsx
rename to web/src/components/canvas/canvas-node.tsx
index 0fd9cd8..281390f 100644
--- a/web/src/app/(user)/canvas/components/canvas-node.tsx
+++ b/web/src/components/canvas/canvas-node.tsx
@@ -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";
diff --git a/web/src/app/(user)/canvas/components/canvas-project-card.tsx b/web/src/components/canvas/canvas-project-card.tsx
similarity index 95%
rename from web/src/app/(user)/canvas/components/canvas-project-card.tsx
rename to web/src/components/canvas/canvas-project-card.tsx
index d90381e..ca306cc 100644
--- a/web/src/app/(user)/canvas/components/canvas-project-card.tsx
+++ b/web/src/components/canvas/canvas-project-card.tsx
@@ -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();
diff --git a/web/src/app/(user)/canvas/components/canvas-prompt-library.tsx b/web/src/components/canvas/canvas-prompt-library.tsx
similarity index 98%
rename from web/src/app/(user)/canvas/components/canvas-prompt-library.tsx
rename to web/src/components/canvas/canvas-prompt-library.tsx
index a29730d..ec73084 100644
--- a/web/src/app/(user)/canvas/components/canvas-prompt-library.tsx
+++ b/web/src/components/canvas/canvas-prompt-library.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { useState } from "react";
import { Button, Tooltip } from "antd";
import { BookOpen } from "lucide-react";
diff --git a/web/src/app/(user)/canvas/components/canvas-resource-mention-textarea.tsx b/web/src/components/canvas/canvas-resource-mention-textarea.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-resource-mention-textarea.tsx
rename to web/src/components/canvas/canvas-resource-mention-textarea.tsx
index 3b13088..4e4d801 100644
--- a/web/src/app/(user)/canvas/components/canvas-resource-mention-textarea.tsx
+++ b/web/src/components/canvas/canvas-resource-mention-textarea.tsx
@@ -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;
diff --git a/web/src/app/(user)/canvas/components/canvas-size-picker.tsx b/web/src/components/canvas/canvas-size-picker.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-size-picker.tsx
rename to web/src/components/canvas/canvas-size-picker.tsx
index 349a3aa..ba84dfa 100644
--- a/web/src/app/(user)/canvas/components/canvas-size-picker.tsx
+++ b/web/src/components/canvas/canvas-size-picker.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { useEffect, useRef, useState } from "react";
import { Select } from "antd";
diff --git a/web/src/app/(user)/canvas/components/canvas-toolbar.tsx b/web/src/components/canvas/canvas-toolbar.tsx
similarity index 100%
rename from web/src/app/(user)/canvas/components/canvas-toolbar.tsx
rename to web/src/components/canvas/canvas-toolbar.tsx
diff --git a/web/src/app/(user)/canvas/components/canvas-video-settings-popover.tsx b/web/src/components/canvas/canvas-video-settings-popover.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/canvas-video-settings-popover.tsx
rename to web/src/components/canvas/canvas-video-settings-popover.tsx
index 4e4c613..d9301a3 100644
--- a/web/src/app/(user)/canvas/components/canvas-video-settings-popover.tsx
+++ b/web/src/components/canvas/canvas-video-settings-popover.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { useEffect, useRef, useState, type RefObject } from "react";
import { createPortal } from "react-dom";
import { Settings2 } from "lucide-react";
diff --git a/web/src/app/(user)/canvas/components/canvas-zoom-controls.tsx b/web/src/components/canvas/canvas-zoom-controls.tsx
similarity index 100%
rename from web/src/app/(user)/canvas/components/canvas-zoom-controls.tsx
rename to web/src/components/canvas/canvas-zoom-controls.tsx
diff --git a/web/src/app/(user)/canvas/components/infinite-canvas.tsx b/web/src/components/canvas/infinite-canvas.tsx
similarity index 99%
rename from web/src/app/(user)/canvas/components/infinite-canvas.tsx
rename to web/src/components/canvas/infinite-canvas.tsx
index f9293da..2d9550e 100644
--- a/web/src/app/(user)/canvas/components/infinite-canvas.tsx
+++ b/web/src/components/canvas/infinite-canvas.tsx
@@ -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;
diff --git a/web/src/components/image-generation-pending.tsx b/web/src/components/image-generation-pending.tsx
index ecaf660..544d954 100644
--- a/web/src/components/image-generation-pending.tsx
+++ b/web/src/components/image-generation-pending.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { useEffect, useState } from "react";
import { LoaderCircle } from "lucide-react";
diff --git a/web/src/components/image-settings-panel.tsx b/web/src/components/image-settings-panel.tsx
index 386bde3..3a1f2ed 100644
--- a/web/src/components/image-settings-panel.tsx
+++ b/web/src/components/image-settings-panel.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { type ReactNode, useState } from "react";
import { ConfigProvider, Switch } from "antd";
diff --git a/web/src/components/layout/app-config-modal.tsx b/web/src/components/layout/app-config-modal.tsx
index 19b581e..04b26b9 100644
--- a/web/src/components/layout/app-config-modal.tsx
+++ b/web/src/components/layout/app-config-modal.tsx
@@ -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";
diff --git a/web/src/components/layout/app-providers.tsx b/web/src/components/layout/app-providers.tsx
index 5d6e99e..1eaccdd 100644
--- a/web/src/components/layout/app-providers.tsx
+++ b/web/src/components/layout/app-providers.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import type { ReactNode } from "react";
import { useEffect } from "react";
import { ProConfigProvider } from "@ant-design/pro-components";
diff --git a/web/src/components/layout/app-top-nav.tsx b/web/src/components/layout/app-top-nav.tsx
index b421b62..326bf41 100644
--- a/web/src/components/layout/app-top-nav.tsx
+++ b/web/src/components/layout/app-top-nav.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { Menu } from "lucide-react";
import { Link, useLocation } from "react-router-dom";
diff --git a/web/src/components/layout/client-root-init.tsx b/web/src/components/layout/client-root-init.tsx
index 1b00c9b..139af77 100644
--- a/web/src/components/layout/client-root-init.tsx
+++ b/web/src/components/layout/client-root-init.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import type { ReactNode } from "react";
import { useEffect, useRef } from "react";
import { App } from "antd";
diff --git a/web/src/components/layout/github-link.tsx b/web/src/components/layout/github-link.tsx
index eee8208..ca695b2 100644
--- a/web/src/components/layout/github-link.tsx
+++ b/web/src/components/layout/github-link.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { GithubOutlined } from "@ant-design/icons";
import { cn } from "@/lib/utils";
diff --git a/web/src/components/layout/mobile-nav-drawer.tsx b/web/src/components/layout/mobile-nav-drawer.tsx
index 4ec1ca9..c600916 100644
--- a/web/src/components/layout/mobile-nav-drawer.tsx
+++ b/web/src/components/layout/mobile-nav-drawer.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { Drawer } from "antd";
import { Link } from "react-router-dom";
diff --git a/web/src/components/layout/user-status-actions.tsx b/web/src/components/layout/user-status-actions.tsx
index abd2e73..44bbe50 100644
--- a/web/src/components/layout/user-status-actions.tsx
+++ b/web/src/components/layout/user-status-actions.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import type { CSSProperties } from "react";
import { BookOpen, Keyboard, Settings2 } from "lucide-react";
diff --git a/web/src/components/layout/version-release-modal.tsx b/web/src/components/layout/version-release-modal.tsx
index af58172..7f4bf19 100644
--- a/web/src/components/layout/version-release-modal.tsx
+++ b/web/src/components/layout/version-release-modal.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import type { CSSProperties } from "react";
import { Modal, Tag, Timeline } from "antd";
import { useVersionCheck } from "@/hooks/use-version-check";
diff --git a/web/src/components/model-picker.tsx b/web/src/components/model-picker.tsx
index bb2f7ab..1ebf373 100644
--- a/web/src/components/model-picker.tsx
+++ b/web/src/components/model-picker.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { useEffect, useId, useMemo, useState } from "react";
import { Cpu } from "lucide-react";
diff --git a/web/src/components/prompts/prompt-card.tsx b/web/src/components/prompts/prompt-card.tsx
index 31d4ec4..ce8c011 100644
--- a/web/src/components/prompts/prompt-card.tsx
+++ b/web/src/components/prompts/prompt-card.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { Copy } from "lucide-react";
import type { ReactNode } from "react";
import { Button, Card, Tag } from "antd";
diff --git a/web/src/components/prompts/prompt-select-dialog.tsx b/web/src/components/prompts/prompt-select-dialog.tsx
index 8c58586..6f7b4fc 100644
--- a/web/src/components/prompts/prompt-select-dialog.tsx
+++ b/web/src/components/prompts/prompt-select-dialog.tsx
@@ -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";
diff --git a/web/src/components/prompts/use-prompt-list.ts b/web/src/components/prompts/use-prompt-list.ts
index c09f117..b5b8f20 100644
--- a/web/src/components/prompts/use-prompt-list.ts
+++ b/web/src/components/prompts/use-prompt-list.ts
@@ -1,5 +1,3 @@
-"use client";
-
import { useMemo } from "react";
import { useInfiniteQuery } from "@tanstack/react-query";
diff --git a/web/src/components/ui/animated-theme-toggler.tsx b/web/src/components/ui/animated-theme-toggler.tsx
index 717db7b..56c0908 100644
--- a/web/src/components/ui/animated-theme-toggler.tsx
+++ b/web/src/components/ui/animated-theme-toggler.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { useCallback, useEffect, useRef, useState } from "react";
import { Moon, Sun } from "lucide-react";
import { flushSync } from "react-dom";
diff --git a/web/src/components/ui/dia-text-reveal.tsx b/web/src/components/ui/dia-text-reveal.tsx
index 45ebf8d..373cf02 100644
--- a/web/src/components/ui/dia-text-reveal.tsx
+++ b/web/src/components/ui/dia-text-reveal.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { useEffect, useRef, useState } from "react";
import { animate, motion, useInView, useMotionValue, useReducedMotion, useTransform, type HTMLMotionProps } from "motion/react";
diff --git a/web/src/components/ui/select.tsx b/web/src/components/ui/select.tsx
index 414714f..c9bb249 100644
--- a/web/src/components/ui/select.tsx
+++ b/web/src/components/ui/select.tsx
@@ -1,5 +1,3 @@
-"use client"
-
import * as React from "react"
import { Select as SelectPrimitive } from "radix-ui"
diff --git a/web/src/components/video-settings-panel.tsx b/web/src/components/video-settings-panel.tsx
index ab7bce2..9fb977d 100644
--- a/web/src/components/video-settings-panel.tsx
+++ b/web/src/components/video-settings-panel.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { type ReactNode } from "react";
import { Switch } from "antd";
diff --git a/web/src/app/(user)/canvas/constants.ts b/web/src/constant/canvas.ts
similarity index 93%
rename from web/src/app/(user)/canvas/constants.ts
rename to web/src/constant/canvas.ts
index 3029ec7..d1f02b6 100644
--- a/web/src/app/(user)/canvas/constants.ts
+++ b/web/src/constant/canvas.ts
@@ -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;
diff --git a/web/src/hooks/use-copy-text.ts b/web/src/hooks/use-copy-text.ts
index 80f1eea..9d2c6ff 100644
--- a/web/src/hooks/use-copy-text.ts
+++ b/web/src/hooks/use-copy-text.ts
@@ -1,5 +1,3 @@
-"use client";
-
import { App } from "antd";
import copy from "copy-to-clipboard";
diff --git a/web/src/app/(user)/layout.tsx b/web/src/layouts/user-layout.tsx
similarity index 96%
rename from web/src/app/(user)/layout.tsx
rename to web/src/layouts/user-layout.tsx
index 9fa65bf..752d71a 100644
--- a/web/src/app/(user)/layout.tsx
+++ b/web/src/layouts/user-layout.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import type { ReactNode } from "react";
import { AppTopNav } from "@/components/layout/app-top-nav";
diff --git a/web/src/app/(user)/canvas/utils/canvas-agent-ops.ts b/web/src/lib/canvas/canvas-agent-ops.ts
similarity index 97%
rename from web/src/app/(user)/canvas/utils/canvas-agent-ops.ts
rename to web/src/lib/canvas/canvas-agent-ops.ts
index 335d0f0..5c2a2e9 100644
--- a/web/src/app/(user)/canvas/utils/canvas-agent-ops.ts
+++ b/web/src/lib/canvas/canvas-agent-ops.ts
@@ -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 }
diff --git a/web/src/app/(user)/canvas/utils/canvas-export.ts b/web/src/lib/canvas/canvas-export.ts
similarity index 94%
rename from web/src/app/(user)/canvas/utils/canvas-export.ts
rename to web/src/lib/canvas/canvas-export.ts
index 5e91aba..f4c41a5 100644
--- a/web/src/app/(user)/canvas/utils/canvas-export.ts
+++ b/web/src/lib/canvas/canvas-export.ts
@@ -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 }[] = [];
diff --git a/web/src/app/(user)/canvas/utils/canvas-image-data.ts b/web/src/lib/canvas/canvas-image-data.ts
similarity index 99%
rename from web/src/app/(user)/canvas/utils/canvas-image-data.ts
rename to web/src/lib/canvas/canvas-image-data.ts
index a91f34d..c9fd8e4 100644
--- a/web/src/app/(user)/canvas/utils/canvas-image-data.ts
+++ b/web/src/lib/canvas/canvas-image-data.ts
@@ -1,5 +1,3 @@
-"use client";
-
export type ImageCropRect = {
x: number;
y: number;
diff --git a/web/src/app/(user)/canvas/utils/canvas-node-size.ts b/web/src/lib/canvas/canvas-node-size.ts
similarity index 98%
rename from web/src/app/(user)/canvas/utils/canvas-node-size.ts
rename to web/src/lib/canvas/canvas-node-size.ts
index fdce716..b43bb8e 100644
--- a/web/src/app/(user)/canvas/utils/canvas-node-size.ts
+++ b/web/src/lib/canvas/canvas-node-size.ts
@@ -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);
diff --git a/web/src/app/(user)/canvas/utils/canvas-resource-references.ts b/web/src/lib/canvas/canvas-resource-references.ts
similarity index 99%
rename from web/src/app/(user)/canvas/utils/canvas-resource-references.ts
rename to web/src/lib/canvas/canvas-resource-references.ts
index 3e77499..cf4b23d 100644
--- a/web/src/app/(user)/canvas/utils/canvas-resource-references.ts
+++ b/web/src/lib/canvas/canvas-resource-references.ts
@@ -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";
diff --git a/web/src/main.tsx b/web/src/main.tsx
index 3c06649..ffde538 100644
--- a/web/src/main.tsx
+++ b/web/src/main.tsx
@@ -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";
diff --git a/web/src/app/(user)/assets/asset-transfer.ts b/web/src/pages/assets/asset-transfer.ts
similarity index 100%
rename from web/src/app/(user)/assets/asset-transfer.ts
rename to web/src/pages/assets/asset-transfer.ts
diff --git a/web/src/app/(user)/assets/page.tsx b/web/src/pages/assets/index.tsx
similarity index 99%
rename from web/src/app/(user)/assets/page.tsx
rename to web/src/pages/assets/index.tsx
index 38c4f49..56ef703 100644
--- a/web/src/app/(user)/assets/page.tsx
+++ b/web/src/pages/assets/index.tsx
@@ -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";
diff --git a/web/src/app/(user)/canvas/page.tsx b/web/src/pages/canvas/index.tsx
similarity index 93%
rename from web/src/app/(user)/canvas/page.tsx
rename to web/src/pages/canvas/index.tsx
index 0942600..8fd37ca 100644
--- a/web/src/app/(user)/canvas/page.tsx
+++ b/web/src/pages/canvas/index.tsx
@@ -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();
diff --git a/web/src/app/(user)/canvas/[id]/canvas-client-page.tsx b/web/src/pages/canvas/project.tsx
similarity index 98%
rename from web/src/app/(user)/canvas/[id]/canvas-client-page.tsx
rename to web/src/pages/canvas/project.tsx
index a5dd125..c757b4f 100644
--- a/web/src/app/(user)/canvas/[id]/canvas-client-page.tsx
+++ b/web/src/pages/canvas/project.tsx
@@ -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";
diff --git a/web/src/app/(user)/page.tsx b/web/src/pages/home/index.tsx
similarity index 99%
rename from web/src/app/(user)/page.tsx
rename to web/src/pages/home/index.tsx
index e7174d8..7007733 100644
--- a/web/src/app/(user)/page.tsx
+++ b/web/src/pages/home/index.tsx
@@ -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";
diff --git a/web/src/app/(user)/image/page.tsx b/web/src/pages/image/index.tsx
similarity index 99%
rename from web/src/app/(user)/image/page.tsx
rename to web/src/pages/image/index.tsx
index c7b50ac..78314ae 100644
--- a/web/src/app/(user)/image/page.tsx
+++ b/web/src/pages/image/index.tsx
@@ -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";
diff --git a/web/src/app/not-found.tsx b/web/src/pages/not-found/index.tsx
similarity index 100%
rename from web/src/app/not-found.tsx
rename to web/src/pages/not-found/index.tsx
diff --git a/web/src/components/prompts/prompt-detail-dialog.tsx b/web/src/pages/prompts/components/prompt-detail-dialog.tsx
similarity index 99%
rename from web/src/components/prompts/prompt-detail-dialog.tsx
rename to web/src/pages/prompts/components/prompt-detail-dialog.tsx
index e32501f..b1bda43 100644
--- a/web/src/components/prompts/prompt-detail-dialog.tsx
+++ b/web/src/pages/prompts/components/prompt-detail-dialog.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import { Copy, FolderPlus } from "lucide-react";
import { Button, Modal, Space, Tag } from "antd";
diff --git a/web/src/app/(user)/prompts/page.tsx b/web/src/pages/prompts/index.tsx
similarity index 98%
rename from web/src/app/(user)/prompts/page.tsx
rename to web/src/pages/prompts/index.tsx
index 4d511ef..04a5f28 100644
--- a/web/src/app/(user)/prompts/page.tsx
+++ b/web/src/pages/prompts/index.tsx
@@ -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";
diff --git a/web/src/app/(user)/video/page.tsx b/web/src/pages/video/index.tsx
similarity index 99%
rename from web/src/app/(user)/video/page.tsx
rename to web/src/pages/video/index.tsx
index 1a07bb0..f896f27 100644
--- a/web/src/app/(user)/video/page.tsx
+++ b/web/src/pages/video/index.tsx
@@ -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";
diff --git a/web/src/router.tsx b/web/src/router.tsx
index 82e742e..dc00819 100644
--- a/web/src/router.tsx
+++ b/web/src/router.tsx
@@ -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([
),
children: [
- { path: "/", element: },
+ { path: "/", element: },
{ path: "/image", element: },
{ path: "/video", element: },
{ path: "/assets", element: },
diff --git a/web/src/services/api/prompts.ts b/web/src/services/api/prompts.ts
index 25bb039..62e658d 100644
--- a/web/src/services/api/prompts.ts
+++ b/web/src/services/api/prompts.ts
@@ -1,5 +1,3 @@
-"use client";
-
import localforage from "localforage";
export type Prompt = {
diff --git a/web/src/services/app-sync.ts b/web/src/services/app-sync.ts
index 1f4a976..e08f569 100644
--- a/web/src/services/app-sync.ts
+++ b/web/src/services/app-sync.ts
@@ -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 & { id?: string };
export type AppSyncDomainKey = "canvas" | "assets" | "image-workbench" | "video-workbench";
diff --git a/web/src/services/file-storage.ts b/web/src/services/file-storage.ts
index 60ee07a..fce89d4 100644
--- a/web/src/services/file-storage.ts
+++ b/web/src/services/file-storage.ts
@@ -1,5 +1,3 @@
-"use client";
-
import localforage from "localforage";
import { nanoid } from "nanoid";
diff --git a/web/src/services/image-storage.ts b/web/src/services/image-storage.ts
index 6729198..7e3079f 100644
--- a/web/src/services/image-storage.ts
+++ b/web/src/services/image-storage.ts
@@ -1,5 +1,3 @@
-"use client";
-
import localforage from "localforage";
import { nanoid } from "nanoid";
diff --git a/web/src/services/webdav-sync.ts b/web/src/services/webdav-sync.ts
index dc6dda8..166a963 100644
--- a/web/src/services/webdav-sync.ts
+++ b/web/src/services/webdav-sync.ts
@@ -1,5 +1,3 @@
-"use client";
-
import type { WebdavSyncConfig } from "@/stores/use-config-store";
export const WEBDAV_MANIFEST_FILE_NAME = "manifest.json";
diff --git a/web/src/app/(user)/canvas/stores/use-canvas-agent-store.ts b/web/src/stores/canvas/use-canvas-agent-store.ts
similarity index 97%
rename from web/src/app/(user)/canvas/stores/use-canvas-agent-store.ts
rename to web/src/stores/canvas/use-canvas-agent-store.ts
index ed9b022..54bba90 100644
--- a/web/src/app/(user)/canvas/stores/use-canvas-agent-store.ts
+++ b/web/src/stores/canvas/use-canvas-agent-store.ts
@@ -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 };
diff --git a/web/src/app/(user)/canvas/stores/use-canvas-store.ts b/web/src/stores/canvas/use-canvas-store.ts
similarity index 99%
rename from web/src/app/(user)/canvas/stores/use-canvas-store.ts
rename to web/src/stores/canvas/use-canvas-store.ts
index 1a17590..412d236 100644
--- a/web/src/app/(user)/canvas/stores/use-canvas-store.ts
+++ b/web/src/stores/canvas/use-canvas-store.ts
@@ -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;
diff --git a/web/src/app/(user)/canvas/stores/use-canvas-ui-store.ts b/web/src/stores/canvas/use-canvas-ui-store.ts
similarity index 100%
rename from web/src/app/(user)/canvas/stores/use-canvas-ui-store.ts
rename to web/src/stores/canvas/use-canvas-ui-store.ts
diff --git a/web/src/stores/use-asset-store.ts b/web/src/stores/use-asset-store.ts
index e643637..12ddf1e 100644
--- a/web/src/stores/use-asset-store.ts
+++ b/web/src/stores/use-asset-store.ts
@@ -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()(
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);
diff --git a/web/src/stores/use-config-store.ts b/web/src/stores/use-config-store.ts
index 981f45c..ca9d510 100644
--- a/web/src/stores/use-config-store.ts
+++ b/web/src/stores/use-config-store.ts
@@ -1,5 +1,3 @@
-"use client";
-
import { useMemo } from "react";
import { create } from "zustand";
import { persist } from "zustand/middleware";
diff --git a/web/src/stores/use-user-store.ts b/web/src/stores/use-user-store.ts
index bdf3ec2..46f19af 100644
--- a/web/src/stores/use-user-store.ts
+++ b/web/src/stores/use-user-store.ts
@@ -1,5 +1,3 @@
-"use client";
-
import { create } from "zustand";
export type LocalUser = {
diff --git a/web/src/app/globals.css b/web/src/styles/globals.css
similarity index 100%
rename from web/src/app/globals.css
rename to web/src/styles/globals.css
diff --git a/web/src/app/(user)/canvas/export-types.ts b/web/src/types/canvas-export.ts
similarity index 83%
rename from web/src/app/(user)/canvas/export-types.ts
rename to web/src/types/canvas-export.ts
index e48596e..1df7d55 100644
--- a/web/src/app/(user)/canvas/export-types.ts
+++ b/web/src/types/canvas-export.ts
@@ -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";
diff --git a/web/src/app/(user)/canvas/types.ts b/web/src/types/canvas.ts
similarity index 100%
rename from web/src/app/(user)/canvas/types.ts
rename to web/src/types/canvas.ts