Replicate target video canvas frontend
This commit is contained in:
parent
0edd9872ea
commit
799dd3007b
@ -89,6 +89,7 @@ import {
|
||||
import { CanvasWorkspace } from "@/components/canvas";
|
||||
import { CanvasChatPanel } from "@/components/canvas/CanvasChatPanel";
|
||||
import { CanvasFileListPanel } from "@/components/canvas/CanvasFileListPanel";
|
||||
import { LiblibVideoCanvasReplica } from "@/components/canvas/LiblibVideoCanvasReplica";
|
||||
import { ObjectPickerOverlay } from "@/components/canvas/ObjectPickerOverlay";
|
||||
import { SceneFusionDialog } from "@/components/scene-fusion-dialog";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@ -1340,6 +1341,24 @@ function getFirstResultUrlFromCollections(
|
||||
}
|
||||
|
||||
export default function CanvasEditorPage({ params }: PageProps) {
|
||||
const routeParams = useParams();
|
||||
const searchParams = useSearchParams();
|
||||
const id = (routeParams?.id as string) || use(params).id;
|
||||
const mode = searchParams.get("mode");
|
||||
const requestedWorkspaceType = searchParams.get("workspaceType");
|
||||
const requestedAgentMode = mode === "agent";
|
||||
const isVideoMode =
|
||||
!requestedAgentMode &&
|
||||
(mode === "video" || requestedWorkspaceType === "video");
|
||||
|
||||
if (isVideoMode) {
|
||||
return <LiblibVideoCanvasReplica canvasId={id} />;
|
||||
}
|
||||
|
||||
return <CanvasEditorLegacyPage params={params} />;
|
||||
}
|
||||
|
||||
function CanvasEditorLegacyPage({ params }: PageProps) {
|
||||
// 使用 useParams 确保客户端导航时能正确获取更新的 id
|
||||
const routeParams = useParams();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
# LibTV frontend capture
|
||||
|
||||
Captured URL: https://www.liblib.tv/canvas?projectId=ed9a2c12570a49b9a1186017b2c38b52
|
||||
|
||||
This folder contains the browser-delivered frontend assets and API responses that were accessible with the logged-in Chrome session:
|
||||
|
||||
- `snapshot.html`: hydrated page HTML snapshot after opening the canvas.
|
||||
- `assets/`: downloaded JS/CSS/static assets loaded or referenced by the page.
|
||||
- `api/`: captured JSON/API responses and worker/icon resources where applicable.
|
||||
- `network.har`: embedded HAR of the capture session.
|
||||
- `manifest.json`: URL to local file mapping.
|
||||
- `project-detail.json`: canvas project detail API response.
|
||||
|
||||
Limits: this is not the private source repository or server-side code. Backend logic, model generation services, database code, and non-public source files are not delivered to the browser and cannot be recovered from the page. The project itself is currently empty: `nodes` and `edges` are both empty.
|
||||
|
||||
Public sourcemaps downloaded: 0.
|
||||
|
||||
## Added readable analysis
|
||||
|
||||
- `readable/`: formatted copies of JS/CSS/HTML and pretty-printed JSON.
|
||||
- `api-endpoints.md` / `api-endpoints.json`: grouped endpoint inventory.
|
||||
- `feature-map.md`: observed UI features mapped to network calls.
|
||||
- `keyword-index.md` / `keyword-index.json`: keyword hits inside formatted JS bundles.
|
||||
- `network.sanitized.har`: HAR with cookie/auth-like fields redacted.
|
||||
- `deep-completeness-review.md`: final layered completeness review of what is complete, what is partial, and what cannot be recovered from a browser capture.
|
||||
|
||||
The original web app did not expose public sourcemaps, so `readable/js` is formatted production bundle code, not the original authored TypeScript/React source tree.
|
||||
|
||||
## Module Split
|
||||
|
||||
The production Turbopack chunks have also been split into per-module factory files for easier reading and searching.
|
||||
|
||||
- Module files: `module-split/`
|
||||
- Module index: `module-split/module-index.md` and `module-split/module-index.json`
|
||||
- Module keyword index: `module-split/module-keyword-index.md` and `module-split/module-keyword-index.json`
|
||||
- Extracted modules: 1872 from 70 Turbopack chunks
|
||||
|
||||
## Completeness Audit
|
||||
|
||||
A static and safe dynamic-click audit was run after the initial capture.
|
||||
|
||||
- Audit report: `capture-audit.md` / `capture-audit.json`
|
||||
- Deep review: `deep-completeness-review.md`
|
||||
- Dynamic Skills audit: `dynamic-audit/`
|
||||
- Dynamic toolbar/add-node/share audit: `dynamic-audit-toolbars/`
|
||||
- New JS/CSS/chunk resources found dynamically: 0
|
||||
- New JSON/media resources saved dynamically: 17
|
||||
|
||||
Security note: the raw capture folder and full archive may include logged-in session traces. Use the sanitized archive for sharing.
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,101 @@
|
||||
# LibLib Canvas 前端获取完整性深度审计
|
||||
|
||||
生成时间:2026-04-18
|
||||
项目地址:https://www.liblib.tv/canvas?projectId=ed9a2c12570a49b9a1186017b2c38b52
|
||||
|
||||
## 结论
|
||||
|
||||
这份抓取对于“浏览器实际交付到当前登录画布页的生产前端代码”完整性很高:静态 HTML、JS、CSS、字体、图片、主要 API JSON、画布工具配置、动态安全点击后新增的 JSON/图片资源都已落盘;没有发现缺失的浏览器 JS/CSS chunk。
|
||||
|
||||
但它不是原始源码仓库,也不是后端代码。没有公开 sourcemap,因此无法还原成一模一样的 TypeScript/React 源码目录;浏览器也拿不到服务端私有实现、鉴权逻辑、任务调度、支付、上传、生成等后端代码。
|
||||
|
||||
## 已确认完整的部分
|
||||
|
||||
- 静态资源清单:171 条,全部 HTTP 200,本地缺失 0。
|
||||
- 脱敏 HAR:205 条请求,全部 HTTP 200,失败 0。
|
||||
- 浏览器前端包:74 个 JS、11 个 CSS、3 个 HTML、51 个格式化 API JSON。
|
||||
- Turbopack 模块拆分:74 个 JS 文件中提取 70 个 Turbopack chunk,1872 个模块,解析错误 0。
|
||||
- HTML/JS 中引用的完整静态 chunk URL:83 个,清单缺失 0。
|
||||
- HTML 中直接引用的静态 chunk URL:66 个,清单缺失 0。
|
||||
- 画布功能模块配置:67 个 tool spec、25 个 workflow preset 已从接口保存。
|
||||
- 动态安全点击审计:两轮共 281 条响应,新增资源均为 JSON/图片/埋点,没有新增 JS/CSS/chunk。
|
||||
|
||||
## 画布功能模块覆盖
|
||||
|
||||
画布工具配置来自 `/api/tool_spec/list` 与 `/api/canvas/workflow/list`,已整理到 `canvas-modules.json` 和 `canvas-modules.md`。
|
||||
|
||||
已覆盖的工具类型:
|
||||
|
||||
- image:22
|
||||
- video:38
|
||||
- text:3
|
||||
- audio:3
|
||||
- compose:1
|
||||
|
||||
已覆盖的模式:
|
||||
|
||||
- image2image:17
|
||||
- image2text:3
|
||||
- video2text:3
|
||||
- mixed2video:8
|
||||
- frames2video:24
|
||||
- videoEdit2video:3
|
||||
- audio2video:9
|
||||
- singleImage2video:31
|
||||
- image2video:9
|
||||
- video2video:2
|
||||
|
||||
这说明画布中可添加的主要节点、模型、参数、属性枚举、工作流预设已经拿到配置层数据。真正点击“生成”、真实上传素材、真实支付、发布成功后的后端任务响应不属于静态前端配置,未强行触发。
|
||||
|
||||
## 动态交互覆盖
|
||||
|
||||
执行过的安全交互包括:
|
||||
|
||||
- 左侧添加面板及文本、图片、视频、视频组合、音频、脚本、上传、图库入口。
|
||||
- 左侧 workflow、assets、history、help、support。
|
||||
- 顶部 share/publish 相关入口、会员入口、头像入口。
|
||||
- LibTV Skills/access key 相关弹窗入口。
|
||||
|
||||
为避免改动项目,审计时阻断了草稿更新、节点批量写入、版本创建、发布、分享开关等写接口。动态审计新增资源包括媒体素材 tag tree、会员 modelCards、activity config、若干 PNG/SVG 静态素材;没有新增前端代码包。
|
||||
|
||||
## Worker 与异步资源检查
|
||||
|
||||
- `draft-save.worker.js` 是真实独立 Worker,已抓取为 `assets/draft-save.worker.593c8f9e44.js`,并在 `readable/js/` 中有可读化副本。
|
||||
- `elk-worker.min.js` 字符串出现在 ELK 布局库的 Browserify/Turbopack 内部模块依赖映射中,属于打包进现有 chunk 的内部模块,不是浏览器另行请求的缺失文件。
|
||||
- HLS、代码高亮等第三方库里有 Worker 创建逻辑,但动态审计没有发现它们拉取额外 JS/CSS 静态资源。
|
||||
|
||||
## 当前项目实例状态
|
||||
|
||||
项目详情接口显示该画布项目名为“未命名”,项目草稿里:
|
||||
|
||||
- `nodeList`:0
|
||||
- `connectionList`:null
|
||||
- `draftData.nodes`:0
|
||||
- `draftData.edges`:0
|
||||
|
||||
所以这次拿到的是“空画布页面及其完整可用功能前端/配置”,不是某个已有复杂画布的节点实例数据。
|
||||
|
||||
## 不能算完整的边界
|
||||
|
||||
以下内容不是遗漏,而是浏览器抓包天然拿不到,或出于安全没有触发:
|
||||
|
||||
- 原始源码仓库、源码注释、源码文件名、未压缩组件层级。
|
||||
- 后端服务代码、数据库结构、任务队列、模型调用实现。
|
||||
- 未公开 sourcemap;本地没有 `.map` 文件。
|
||||
- 需要真实消耗积分/付费/上传/生成/删除/发布成功后的动作级响应。
|
||||
- 只有特定账号权限、特定素材、已有节点数据、或服务端灰度开关才会出现的私有返回。
|
||||
|
||||
## 风险提示
|
||||
|
||||
原始抓取目录和全量压缩包可能包含登录态、用户信息或请求头痕迹,仅适合本机分析。对外传递应使用重新生成后的脱敏包,或只传 `readable/`、`module-split/`、`canvas-modules.*`、`feature-map.md`、`api-endpoints.*` 这类分析产物。
|
||||
|
||||
## 最终判断
|
||||
|
||||
完整性分层判断:
|
||||
|
||||
- 浏览器交付的生产前端运行代码:高,可以认为当前访问路径下已完整。
|
||||
- 画布功能模块配置:高,主要工具、模型、参数和 workflow 已保存。
|
||||
- 弹窗和普通 UI 交互前端:中高,安全可点入口已覆盖,未发现新代码包。
|
||||
- 真实业务动作返回:部分,仅保存了已触发/安全触发的 API JSON。
|
||||
- 原始前端源码仓库:不完整,公开页面无法直接取得。
|
||||
- 后端代码:不完整,浏览器无法取得。
|
||||
@ -184,6 +184,7 @@ interface CanvasWorkspaceProps {
|
||||
workspaceVariant?: "canvas" | "video";
|
||||
layerSplitEnabled?: boolean;
|
||||
showEmptyState?: boolean;
|
||||
showZoomControls?: boolean;
|
||||
onBlankCanvasDoubleClick?: (payload: {
|
||||
screenPosition: Position;
|
||||
canvasPosition: Position;
|
||||
@ -197,6 +198,7 @@ export function CanvasWorkspace({
|
||||
workspaceVariant = "canvas",
|
||||
layerSplitEnabled = false,
|
||||
showEmptyState = true,
|
||||
showZoomControls = true,
|
||||
onBlankCanvasDoubleClick,
|
||||
}: CanvasWorkspaceProps) {
|
||||
const SNAP_THRESHOLD = 10;
|
||||
@ -1525,7 +1527,8 @@ export function CanvasWorkspace({
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={cn(
|
||||
"relative w-full h-full overflow-hidden overscroll-none touch-none bg-background",
|
||||
"relative w-full h-full overflow-hidden overscroll-none touch-none",
|
||||
isWorkflowCanvas ? "bg-[#050607] text-white" : "bg-background",
|
||||
tool === 'pan' && "cursor-grab",
|
||||
tool === 'draw' && "cursor-crosshair",
|
||||
isPanning && "cursor-grabbing"
|
||||
@ -1544,12 +1547,12 @@ export function CanvasWorkspace({
|
||||
>
|
||||
{isWorkflowCanvas ? (
|
||||
<>
|
||||
<div className="pointer-events-none absolute inset-0 pattern-grid-lg text-foreground opacity-[0.028]" />
|
||||
<div className="pointer-events-none absolute inset-0 pattern-grid-lg text-white opacity-[0.065]" />
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0"
|
||||
style={{
|
||||
background:
|
||||
"radial-gradient(circle at top left, rgba(91,118,254,0.05), transparent 26%), radial-gradient(circle at bottom right, rgba(164,176,220,0.08), transparent 26%)",
|
||||
"radial-gradient(circle at top left, rgba(91,118,254,0.08), transparent 24%), radial-gradient(circle at bottom right, rgba(164,176,220,0.05), transparent 28%)",
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
@ -1830,52 +1833,54 @@ export function CanvasWorkspace({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
zoomOverlayClassName,
|
||||
isWorkflowCanvas
|
||||
? "flex items-center gap-1.5 px-1.5 py-1.5"
|
||||
: "flex items-center gap-1 px-1 py-1 border border-border/50"
|
||||
)}
|
||||
data-canvas-overlay
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn("h-7 w-7", isWorkflowCanvas && "rounded-full")}
|
||||
onClick={() => {
|
||||
const newZoom = Math.max(0.1, viewport.zoom - 0.1);
|
||||
setViewport({ zoom: newZoom });
|
||||
}}
|
||||
title="缩小"
|
||||
>
|
||||
<Minus className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
{showZoomControls ? (
|
||||
<div
|
||||
className={cn(
|
||||
"h-7 min-w-[50px] text-xs font-medium",
|
||||
isWorkflowCanvas && "rounded-full px-3"
|
||||
zoomOverlayClassName,
|
||||
isWorkflowCanvas
|
||||
? "flex items-center gap-1.5 px-1.5 py-1.5"
|
||||
: "flex items-center gap-1 px-1 py-1 border border-border/50"
|
||||
)}
|
||||
onClick={() => setViewport({ zoom: 1, x: 0, y: 0 })}
|
||||
title="重置缩放"
|
||||
data-canvas-overlay
|
||||
>
|
||||
{Math.round(viewport.zoom * 100)}%
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn("h-7 w-7", isWorkflowCanvas && "rounded-full")}
|
||||
onClick={() => {
|
||||
const newZoom = Math.min(4, viewport.zoom + 0.1);
|
||||
setViewport({ zoom: newZoom });
|
||||
}}
|
||||
title="放大"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn("h-7 w-7", isWorkflowCanvas && "rounded-full")}
|
||||
onClick={() => {
|
||||
const newZoom = Math.max(0.1, viewport.zoom - 0.1);
|
||||
setViewport({ zoom: newZoom });
|
||||
}}
|
||||
title="缩小"
|
||||
>
|
||||
<Minus className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={cn(
|
||||
"h-7 min-w-[50px] text-xs font-medium",
|
||||
isWorkflowCanvas && "rounded-full px-3"
|
||||
)}
|
||||
onClick={() => setViewport({ zoom: 1, x: 0, y: 0 })}
|
||||
title="重置缩放"
|
||||
>
|
||||
{Math.round(viewport.zoom * 100)}%
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={cn("h-7 w-7", isWorkflowCanvas && "rounded-full")}
|
||||
onClick={() => {
|
||||
const newZoom = Math.min(4, viewport.zoom + 0.1);
|
||||
setViewport({ zoom: newZoom });
|
||||
}}
|
||||
title="放大"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
1735
components/canvas/LiblibVideoCanvasReplica.tsx
Normal file
1735
components/canvas/LiblibVideoCanvasReplica.tsx
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user