dsh-film 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -8
- package/client/client.js +335 -3
- package/client/client.workbench.js +113 -3
- package/lib/agent/media-task-tools.js +3 -3
- package/lib/media/tasks.js +2 -2
- package/lib/studio/media-routes.js +2 -2
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
在 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 和 VibeDev 里做片:聊天右侧的侧栏多出三个标签——**剧本、分镜画布、导演台**。左边和 Agent 聊,右边看和改成果。项目是工作区里 `film/` 下的普通文件;Agent 用专门的影视工具改剧本和分镜、调度导演台(见下),和右边看到的是同一份。
|
|
4
4
|
|
|
5
|
-
> 0.2 仍属预览。分镜画布(含导演台)用的是 VibeDev 的原版前端,跑在插件自己的接口上;剧本标签是按 DSH 界面重写的,文件格式与 VibeDev Studio
|
|
5
|
+
> 0.2 仍属预览。分镜画布(含导演台)用的是 VibeDev 的原版前端,跑在插件自己的接口上;剧本标签是按 DSH 界面重写的,文件格式与 VibeDev Studio 的编剧台相同。图片、视频的生成由可选的「VibeDev 账号与模型」插件 [@vibedev-si/dsh-vibedev](https://github.com/VibeDev-Si/dsh-vibedev)(原 dsh-media)负责。
|
|
6
6
|
|
|
7
7
|
A film workbench for DeepSeek Harness and VibeDev: three right-sidebar tabs — script, storyboard and director desk — working on one film per workspace, kept as plain files under `film/`. In this 0.2 preview, the storyboard (with the director desk) is VibeDev's original front end running on the plugin's own API, and the script tab is a DSH-native rewrite. The agent works on the same film through its own film tools.
|
|
8
8
|
|
|
@@ -37,16 +37,16 @@ A film workbench for DeepSeek Harness and VibeDev: three right-sidebar tabs —
|
|
|
37
37
|
**从 GitHub Release 安装**:插件页的“包名或地址”和上面的命令也接受 Release 里的 `.tgz` 直链,例如
|
|
38
38
|
|
|
39
39
|
```
|
|
40
|
-
https://github.com/
|
|
40
|
+
https://github.com/VibeDev-Si/dsh-film/releases/download/v0.3.2/dsh-film-0.3.2.tgz
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
直链不经过安装源,需要本机能直接访问 GitHub(或配好代理)。包约 3 MB。网速很慢时仍可能超过 pnpm 默认的 60 秒下载时限而失败:这时改为按包名从中国大陆镜像源安装;也可以把时限调长:pnpm 11(DeepSeek Harness 和 VibeDev 桌面版自带的就是它)不再从 `~/.npmrc` 读取这个设置,要在 pnpm 的全局配置 `config.yaml` 里加一行 `fetchTimeout: 600000`(Windows:`%LOCALAPPDATA%\pnpm\config\config.yaml`;macOS:`~/Library/Preferences/pnpm/config.yaml`;Linux:`~/.config/pnpm/config.yaml`),或设环境变量 `pnpm_config_fetch_timeout=600000`;pnpm 10 及更早的版本则在 `~/.npmrc` 里加 `fetch-timeout=600000`。
|
|
44
44
|
|
|
45
45
|
**刚发布的版本**:pnpm 11 默认只安装发布满一天的版本(`minimumReleaseAge`)。第一个版本不受影响:按包名安装一个此前没有任何版本的包时,pnpm 照样装上它,并把它记进 `minimumReleaseAgeExclude`。之后每次发布新版本,头一天按包名安装得到的是上一个版本;要马上装新版本,按确切版本安装(包名写成 `dsh-film@<版本>`),或者用上面的 Release 直链。
|
|
46
46
|
|
|
47
|
-
**不能从 Git 仓库安装**:不要在插件页填仓库地址 `https://github.com/
|
|
47
|
+
**不能从 Git 仓库安装**:不要在插件页填仓库地址 `https://github.com/VibeDev-Si/dsh-film`,也不要对它运行 `dsh plugin add`。`lib/`、`client/` 和 `apps/`(分镜画布和导演台的前端)都是构建产物,不在 git 里,从仓库安装会失败(不会留下装了却用不了的插件)。
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
**「VibeDev 账号与模型」插件是可选的**:只有在分镜画布里生成图片和视频才需要另装 [@vibedev-si/dsh-vibedev](https://github.com/VibeDev-Si/dsh-vibedev)(原 dsh-media,包名 `@vibedev-si/dsh-vibedev`,装法相同;在侧边栏底部或「设置 → VibeDev 账号」登录 VibeDev,走 VibeDev 网关,按用量从 VibeDev 余额扣费)。不装也能写剧本、排分镜和调度导演台;从 VibeDev 插件中心安装影视工作台时会把它一并装上。分镜画布给每个视频模型列出的模式与该插件实际接受的一致:声明不收首帧的线路(Seedance 2.5(VibeDev)、2.5 30s(VibeDev)、2.5 480p 30s(山海)和三条 lec 线路;Seedance 2.5 特价按次收首帧,仍有图生视频)不显示“图生视频”,接一张图时改走“全能参考”,图作为参考图发送;以前保存成“图生视频”的节点在这些线路上也按全能参考生成。
|
|
50
50
|
|
|
51
51
|
装好后打开任意会话的右侧侧栏,在“开始”页点“剧本”等入口。会话需要有工作区。第一次打开任一影视标签时,插件自动在工作区里建好影片(片名取工作区文件夹名,画幅 16:9,连同一张空白分镜画布),不用填表。片名和画幅在三个标签共用的标题栏里改:点片名直接改(最多 80 字,回车或点别处保存,Esc 取消);画幅是这部片子的画面比例,分镜画布和导演台都按它来,菜单里有 16:9、9:16、1:1、4:5、21:9、2.39:1。0.1.0 建的 4:3 影片照旧保留并显示为 4:3。
|
|
52
52
|
|
|
@@ -56,13 +56,25 @@ Requires DeepSeek Harness 0.2.x or VibeDev (VibeDev Next), which is built on it.
|
|
|
56
56
|
|
|
57
57
|
- **Plugin page** (recommended): Plugins → Add plugin → install a third-party plugin by package name, enter `dsh-film`, pick the npm registry or the China mirror under the install source, and install.
|
|
58
58
|
- **Command line**: for the DeepSeek Harness desktop app, quit it fully, then run `dsh plugin --profile desktop add dsh-film` (the `dsh` command is installed from the app menu). VibeDev Next's command is `vibedev-app` (app menu → Manage vibedev-app Command…): quit the app fully, then run `vibedev-app plugin --profile desktop add dsh-film`; it keeps its data in `~/.vibedev-app`, which `dsh` does not reach. With the npm `dsh` CLI, run `dsh plugin add dsh-film`.
|
|
59
|
-
- **GitHub Release**: the plugin page and the commands also take the release asset URL, e.g. `https://github.com/
|
|
59
|
+
- **GitHub Release**: the plugin page and the commands also take the release asset URL, e.g. `https://github.com/VibeDev-Si/dsh-film/releases/download/v0.3.2/dsh-film-0.3.2.tgz`. It is fetched straight from GitHub, not through a registry. The file is about 3 MB; if a very slow connection still hits pnpm's default 60-second fetch timeout, install by name from the mirror instead, or raise the timeout — pnpm 11 (the one the desktop apps ship) no longer reads it from `~/.npmrc`, so add `fetchTimeout: 600000` to pnpm's global `config.yaml` (Windows `%LOCALAPPDATA%\pnpm\config\config.yaml`, macOS `~/Library/Preferences/pnpm/config.yaml`, Linux `~/.config/pnpm/config.yaml`) or set `pnpm_config_fetch_timeout=600000`; pnpm 10 and earlier take `fetch-timeout=600000` in `~/.npmrc`.
|
|
60
60
|
- **A fresh release**: pnpm 11 installs only versions published at least a day ago (`minimumReleaseAge`). The very first version is not held back: installing by name a package with no earlier version installs it and adds it to `minimumReleaseAgeExclude`. After each later release, an install by name gets the previous version for a day; to get the new one at once, install the exact version (`dsh-film@<version>`) or use the release URL.
|
|
61
61
|
- **Not from the git repository**: `lib/`, `client/` and `apps/` (the storyboard and director desk front ends) are build outputs that are not in git, so installing from the repository URL fails (it does not leave a plugin that cannot work).
|
|
62
|
-
- **
|
|
62
|
+
- **The VibeDev account & models plugin is optional**: only image and video generation on the storyboard needs [@vibedev-si/dsh-vibedev](https://github.com/VibeDev-Si/dsh-vibedev) (formerly dsh-media; sign in to VibeDev at the bottom of the sidebar or in Settings → VibeDev account). Everything else works without it; installing the workbench from the VibeDev Plugin Center installs it too. The storyboard lists for each video model the modes that plugin accepts: lanes that declare no first frame (Seedance 2.5 (VibeDev), 2.5 30s (VibeDev), 2.5 480p 30s (山海) and the three lec lanes; Seedance 2.5 特价按次 takes a first frame and keeps image-to-video) offer no image-to-video, and a single image goes as a 全能参考 (multi-reference) reference image instead, also for nodes saved as image-to-video earlier.
|
|
63
63
|
|
|
64
64
|
Then open the right sidebar of a session that has a workspace and pick a part on its start page. The first time a film tab opens, the plugin creates the film — named after the workspace folder, 16:9, with an empty storyboard — with no form to fill in. Rename it and pick its frame in the header the three tabs share: click the title to edit it (up to 80 characters; Enter or clicking away saves, Esc cancels); the frame is the film's aspect ratio, which the storyboard canvas and the director desk use (16:9, 9:16, 1:1, 4:5, 21:9, 2.39:1). A 4:3 film made by 0.1.0 keeps and shows its frame. To upgrade, install the new version over the old one (no need to uninstall first), then restart DeepSeek Harness / VibeDev: until you do, the workbench shows a banner asking for the restart and does not open the storyboard or the director desk. Your film stays in the workspace's `film/` folder either way.
|
|
65
65
|
|
|
66
|
+
### 0.3.2 版本说明 · 0.3.2 release note
|
|
67
|
+
|
|
68
|
+
- **新增「相关组件」入口**:剧本、分镜画布和导演台三个标签的尾部多了一张卡片,按宿主真实的安装情况,缺哪个 VibeDev 组件就逐条说明它管什么——不装「[VibeDev 账号与模型](https://github.com/VibeDev-Si/dsh-vibedev)」就生成不了图片和视频,不装「媒体预览与画廊」就不能在右侧栏直接播生成的结果;点一下打开 VibeDev 插件中心(宿主没有中心页面时退回它的安装对话框,再不然给出可以复制的包名),不会自动安装。宿主已经安装或应用自己内置的组件不会重复推荐,两件都有时只给「更多创作工具」这一个入口。升级插件不动工作区 `film/` 下的作品文件,也不影响 VibeDev 账号登录。
|
|
69
|
+
|
|
70
|
+
- **A 相关组件 (related components) entry**: the end of each of the three tabs — script, storyboard and director desk — carries a card that, from what the Host actually has, names each VibeDev component still missing and what it is for — without [VibeDev Account & Models](https://github.com/VibeDev-Si/dsh-vibedev) the storyboard and the director desk cannot generate images or video, and without Media Viewer generated video and audio cannot be played in the sidebar. One click opens the VibeDev plugin centre (the Host's install dialog when it has no centre page, and otherwise a package name to copy) and nothing is installed by it. A component the Host already has, or that the application itself provides, is not offered again: with both present the card offers only "more tools for creators". Updating the plugin neither touches the film/ files in your workspace nor affects your VibeDev sign-in.
|
|
71
|
+
|
|
72
|
+
### 0.3.1 版本说明 · 0.3.1 release note
|
|
73
|
+
|
|
74
|
+
- **配合改名后的「VibeDev 账号与模型」插件**([@vibedev-si/dsh-vibedev](https://github.com/VibeDev-Si/dsh-vibedev),原 dsh-media):未登录时的提示改为指向侧边栏底部「登录 VibeDev」和「设置 → VibeDev 账号」(dsh-media 的设置页里并没有登录入口);缺少插件时的提示写明新的插件名和包名;Agent 工具说明里的 dsh-media 改称 VibeDev 插件。功能没有变化:分镜画布照旧通过 `vibedevMedia` 服务生成,新旧两个插件都提供这个服务。
|
|
75
|
+
|
|
76
|
+
- **For the renamed VibeDev Account & Models plugin** ([@vibedev-si/dsh-vibedev](https://github.com/VibeDev-Si/dsh-vibedev), formerly dsh-media): the signed-out hint now points to "Sign in to VibeDev" at the bottom of the sidebar and to Settings → VibeDev account (dsh-media's settings page had no sign-in); the missing-plugin hint names the new plugin and package; the agent tool descriptions call it the VibeDev plugin. Nothing else changes: the storyboard still generates through the `vibedevMedia` service, which both the old and the new plugin provide.
|
|
77
|
+
|
|
66
78
|
### 0.3.0 版本说明 · 0.3.0 release note
|
|
67
79
|
|
|
68
80
|
- **视频节点上直接剪**:裁剪条(入点出点、磁吸、0.1 秒微调)、在播放头切开、“剪辑”面板一次截多段或多点截帧、选中多个视频拼接(编码一致时插件无损拼接,否则在页面里重新编码)、分离音频、音频截取。生成、下载、存素材都自动用裁剪后的片段;参考视频超出所选模型的限制时,提示“截取前 N 秒 / 选择片段”,不会自动提交。
|
|
@@ -107,7 +119,7 @@ Then open the right sidebar of a session that has a workspace and pick a part on
|
|
|
107
119
|
film/canvas/media/ 生成、导入的素材
|
|
108
120
|
film/canvas/models/ 从工作区导入的模型(GLB、FBX、OBJ)
|
|
109
121
|
film/canvas/imports.json 从工作区导入过的文件(没改过的不再列为可导入)
|
|
110
|
-
media/ dsh-
|
|
122
|
+
media/ VibeDev 插件(dsh-vibedev)生成的素材;和工作区里其他素材一样,用到时放进 film/
|
|
111
123
|
```
|
|
112
124
|
|
|
113
125
|
一个工作区放一部片。已有的 `film/film.json` 不会被覆盖;建影片时已有的画布(哪怕读不出来)不会被覆盖,工作区里原有的画布会沿用它的 id。删除画布留下的记录只挡住被删的那块画布:删掉影片后再建的新片有新 id,照常带上自己的空白画布。工作区在隐藏文件夹(名字以 `.` 开头)或 `.ssh`、`.aws` 等凭据文件夹里时不建片,也不播放、不导入那里的素材。
|
|
@@ -169,7 +181,7 @@ The agent can do the same with the cutting group (see below): `video_clip` sets
|
|
|
169
181
|
- **剧本** `story_query` `story_asset_bindings` `story_create` `story_apply_ops` `story_history` `story_checkpoint` `story_restore` `story_revert`:和剧本标签用同一套接口,按保存的版本号写入(先 dryRun 预览),每次写入都有版本和操作记录,可以单独撤回某次操作。`story_asset_bindings` 给人物、地点、道具和镜头绑定参考图(只认选中的那份字节,五种解析结果分开报告),工作区里 `film/` 以外的图片先放进影片再绑定。
|
|
170
182
|
- **导入导出** `story_import` `story_export`:先预览再导入为新副本(从不覆盖);导出完整 Markdown、仅正文,或连同参考图打成素材包存到 `film/story-exports/`。
|
|
171
183
|
- **剧本到分镜** `story_source` `story_handoff` `story_adopt` `story_impact` `story_director_links`:把保存的人物、场景、道具、场次或镜头读成制作素材,送到画布成为独立的剧本来源卡(可同时准备一个连好线、只待确认提示词的图片节点,不会自动生成);按节点保存的字段显式采用描述或参考图(参考图另存一份字节快照);剧本改动后查看影响了哪些采用、产物和导演镜头;给导演台已保存的机位记下剧本来源。分镜标签不开也能用。
|
|
172
|
-
- **分镜画布** `canvas_list_clients` `canvas_get_state` `canvas_get_selection` `canvas_read_node` `canvas_get_generation_status` `canvas_get_document` `canvas_create_text_nodes` `canvas_create_generation_flow` `canvas_generation_options` `canvas_set_generation_options` `canvas_run_generation` `canvas_connect_nodes` `canvas_delete_nodes` `canvas_apply_ops` `canvas_attach_media`:分镜标签开着时,改动交给页面执行,用户看着它出现,也能撤销;没开时,改动写进保存的画布,下次打开就在。运行生成要分镜页开着。`canvas_attach_media` 把已经生成好的文件(如
|
|
184
|
+
- **分镜画布** `canvas_list_clients` `canvas_get_state` `canvas_get_selection` `canvas_read_node` `canvas_get_generation_status` `canvas_get_document` `canvas_create_text_nodes` `canvas_create_generation_flow` `canvas_generation_options` `canvas_set_generation_options` `canvas_run_generation` `canvas_connect_nodes` `canvas_delete_nodes` `canvas_apply_ops` `canvas_attach_media`:分镜标签开着时,改动交给页面执行,用户看着它出现,也能撤销;没开时,改动写进保存的画布,下次打开就在。运行生成要分镜页开着。`canvas_attach_media` 把已经生成好的文件(如 VibeDev 插件存在 `media/` 的图片,或工作区里别处的图片、视频、音频)放进指定节点,不重复生成。`canvas_generation_options` 和 `canvas_set_generation_options` 列出、设置或清除节点的运镜、相机、提示词技能(含技能节点)、首尾帧和预设(见上文“运镜与相机”“提示词技能与生成预设”),只改节点设置,不发起生成。`canvas_get_generation_status` 还带上页面记在节点上的原因(`error`):批量或 Agent 发起的生成在发出请求前被拒(如参考视频超限、提示词超长),或生成配置节点的输出全部失败;`canvas_get_document` 的节点摘要带上运镜、相机、技能、首尾帧和技能节点的技能。
|
|
173
185
|
- **影片任务** `media_get_task` `media_cancel_task`:读取或取消分镜画布的生成任务(`canvas_get_generation_status` 在 `outputs[].task` 里给出的任务号),以及剪切组发起的出片、拼接和分离音频(完成后 `file.landedNodeId` 是落到画板上的新节点,`file.derivedFrom` 列出各来源实际用到的区间;取消的剪切不留半个文件)。
|
|
174
186
|
- **导演组(按需)** `director_query` `director_models` `director_stage` `director_render` `director_render_status` `director_render_cancel` `director_inspect_model` `director_review` `director_compile_motion` `director_modeling_brief`:读导演台场景(结构、采样、事件、诊断、动作),列出能用的模型文件和尺寸,按指纹分步调度(先 dryRun;`place_model` 按真实尺寸放入模型,见下文“导演台里的模型”),编译动作,管理审阅版本(审阅版只能交给画布生成)。查询和调度在导演标签关着时作用于保存的节点,开着时作用于桌面上的实时场景;渲染、检视模型和审阅版本要导演标签开着。后台无头渲染暂未接入。
|
|
175
187
|
- **建模组(按需)** `space_plan_compile` `model_brief` `model_review` `model_adopt` `model_status` `model_report` `model_cancel`:按毫米写平面,编译成 `film/spaces/` 下导演台能打开的 GLB(先 dryRun 看尺寸、警告和楼梯可达性);为程序化模型准备建模任务,读 `film/models/<id>/model.json` 里的记录、写审阅、记录用户采用的版本。运行模型、拍检查图、导出和回读 GLB 需要无头浏览器,暂未接入。
|
|
@@ -219,6 +231,14 @@ npm run build # 先删掉 lib/ 和 client/,再构建 lib/(Host 一侧
|
|
|
219
231
|
|
|
220
232
|
Only `lib/` and `client/` build from this repository. `apps/` is built by `node scripts/build-apps.mjs canvas` from sibling checkouts — `../canvas` on `feat/dsh-host` (vibedev-canvas) and `../director-desk` on `feat/dsh-procedural-mannequin` (vibedev-director-desk); both are private repositories, so only maintainers can rebuild `apps/`. The script replaces only `apps/canvas`: a checkout that comes from 0.1 must delete the other folders under `apps/` by hand, or the package check refuses to pack. The same step removes the files `scripts/app-excludes.mjs` lists (the director desk's experiment and smoke-test pages, benchmark image, and the model-runtime page with its toolchain, about 10 MB) and writes the app's licences and `THIRD-PARTY-NOTICES.txt` (every production npm dependency the bundle can contain, with version, licence, repository and licence text). `node scripts/build-apps.mjs notices canvas` rewrites only those, warning when a checkout is not at the commit the app's `component-build.json` records; `trim` only removes the excluded files. `prepack` builds and then runs `scripts/check-package.mjs`, which refuses to pack without the app, the licence and notice files (with the Apache License beside the director desk's glTF decoders when it has them), with a canvas catalogue that fails the check the agent's tools apply when they read it, with the FFmpeg AAC encoder still in the canvas build (an `aac-encoder` chunk or `licenses/FFmpeg-LGPL-2.1.txt`; this release does not ship it, and the page re-encodes sound with the browser's own AAC encoder), with anything but `canvas` under `apps/`, with a top-level `vendor/` or `models/`, with excluded files present, with stale files in `lib/`, or with build-machine paths in `lib/` or `client/`. `publishConfig` sends `npm publish` to the npm registry.
|
|
221
233
|
|
|
234
|
+
## 可信发布 · Trusted publishing
|
|
235
|
+
|
|
236
|
+
维护者先在本地构建私有前端、运行类型检查和测试、打出已测 tgz,再为对应源码创建版本标签和附带 `dsh-film-<版本>.tgz` 的正式 GitHub Release。`.github/workflows/publish.yml` 通过 npm OIDC 发布这个原包,不在公共 runner 上重建私有画布和导演台。工作流核对 tag、源码和包内版本、Release 附件摘要;相同版本已发布且字节一致则跳过,内容不同则拒绝;发布后下载 npm 完整包再比对。手动重试未发布版本时,工作流 ref 选对应版本标签。
|
|
237
|
+
|
|
238
|
+
npm Trusted Publisher 填 GitHub owner `VibeDev-Si`、repository `dsh-film`、workflow `publish.yml`,Environment 留空,勾选 **Allow npm publish**;需要管理 `latest` / `next` 时同时勾选 **Allow npm dist-tag**。仓库不保存 npm token。可信发布不授权 `npm deprecate`,弃用操作仍需交互认证。
|
|
239
|
+
|
|
240
|
+
Maintainers build and test locally, including the private front ends, then attach the tested archive to the matching tagged GitHub Release. The OIDC workflow publishes those exact bytes, validates the Release digest and source version, refuses conflicting immutable npm versions, and verifies the published tarball. Configure the owner/repository/workflow above; retries of an unpublished version run on its tag ref. Deprecation uses separate interactive authentication.
|
|
241
|
+
|
|
222
242
|
## 许可 · License
|
|
223
243
|
|
|
224
244
|
本插件以 MIT 许可证发布,见 `LICENSE`。包里还带着别人的代码,许可证和第三方声明放在这些地方:
|
package/client/client.js
CHANGED
|
@@ -22,6 +22,16 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
|
|
|
22
22
|
"runtime.restartOlder": "影视工作台已更新:已安装 {installed},但正在运行的还是更早的版本。请重启 DeepSeek Harness / VibeDev,新版本才会生效;重启之前这里的页面无法使用。",
|
|
23
23
|
"runtime.unknown": "未知版本",
|
|
24
24
|
"runtime.retiredView": "这个标签已不再提供。",
|
|
25
|
+
"suite.missing.title": "还差一件,这份工作才走得完",
|
|
26
|
+
"suite.account.missing": "「VibeDev 账号与模型」:装上并登录后,分镜画布和导演台才能生成图片和视频(按用量从 VibeDev 余额扣费)。在 VibeDev 应用里它已经内置,不用另装。",
|
|
27
|
+
"suite.viewer.missing": "「媒体预览与画廊」:生成好的视频和音频在右侧栏直接播放——拖进度、倍速、循环,还能按文件夹看整批素材。",
|
|
28
|
+
"suite.missing.keep": "补齐之后,已保存的作品、剧本和账号登录都不受影响:升级插件不动工作区里的 film/ 文件。",
|
|
29
|
+
"suite.tools.title": "更多创作工具",
|
|
30
|
+
"suite.tools.hint": "VibeDev 账号与模型、媒体预览与画廊,以及插件中心里的其他创作插件,都列在插件中心,可以一并查看和升级。",
|
|
31
|
+
"suite.open": "在插件中心查看",
|
|
32
|
+
"suite.manual": "也可以打开「插件 → 添加插件」,填入这个包名:",
|
|
33
|
+
"suite.copy": "复制包名",
|
|
34
|
+
"suite.copied": "已复制",
|
|
25
35
|
"story.title": "剧本",
|
|
26
36
|
"story.guide.description": "分场大纲、角色和版本,写好交给分镜画布",
|
|
27
37
|
"board.title": "分镜画布",
|
|
@@ -304,6 +314,16 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
|
|
|
304
314
|
"runtime.restartOlder": "The film workbench was updated: {installed} is installed, but an earlier version is still running. Restart DeepSeek Harness / VibeDev for the new version to take effect; until then the pages here cannot be used.",
|
|
305
315
|
"runtime.unknown": "an unknown version",
|
|
306
316
|
"runtime.retiredView": "This tab is no longer offered.",
|
|
317
|
+
"suite.missing.title": "One piece is still missing for this work",
|
|
318
|
+
"suite.account.missing": "\"VibeDev Account & Models\": once installed and signed in, the storyboard canvas and the director desk can generate images and video (billed by usage against your VibeDev balance). The VibeDev app ships with it built in, so there is nothing to add there.",
|
|
319
|
+
"suite.viewer.missing": "\"Media Viewer\": play the video and audio you generated right in the sidebar — seek, change speed, loop, and browse a whole folder as a gallery.",
|
|
320
|
+
"suite.missing.keep": "Adding these leaves your saved work, screenplays and sign-in untouched: updating a plugin never touches the film/ files in your workspace.",
|
|
321
|
+
"suite.tools.title": "More tools for creators",
|
|
322
|
+
"suite.tools.hint": "VibeDev Account & Models, Media Viewer, and the other creator plugins are all listed in the plugin centre, where they can be reviewed and updated together.",
|
|
323
|
+
"suite.open": "Open the plugin centre",
|
|
324
|
+
"suite.manual": "You can also open Plugins → Add plugin and paste this package name:",
|
|
325
|
+
"suite.copy": "Copy package name",
|
|
326
|
+
"suite.copied": "Copied",
|
|
307
327
|
"story.title": "Script",
|
|
308
328
|
"story.guide.description": "Scene outline, characters and versions, handed to the storyboard when ready",
|
|
309
329
|
"board.title": "Storyboard",
|
|
@@ -576,6 +596,302 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
|
|
|
576
596
|
"sw.impact.locate": "Locate on canvas"
|
|
577
597
|
};
|
|
578
598
|
//#endregion
|
|
599
|
+
//#region src/client/suite.ts
|
|
600
|
+
/**
|
|
601
|
+
* The complementary VibeDev components this plugin's own work leans on, and the
|
|
602
|
+
* way to the plugin centre that lists the rest.
|
|
603
|
+
*
|
|
604
|
+
* What the Host actually has comes from its plugin inventory (a bundle the
|
|
605
|
+
* profile installed, or one the application provides itself); the centre's own
|
|
606
|
+
* page is opened where it is registered, and otherwise the Host's install
|
|
607
|
+
* entry, and otherwise the spec a person can paste into the Plugins page. Every
|
|
608
|
+
* service is read optionally through a property check, so a Host without a
|
|
609
|
+
* plugin manager, without a panel layout, or without the plugin navigation
|
|
610
|
+
* contract keeps this plugin working and simply shows no entry, or the manual
|
|
611
|
+
* one. Nothing here installs anything: the centre and the Plugins page own
|
|
612
|
+
* every install, approval and progress.
|
|
613
|
+
*
|
|
614
|
+
* Package names are the stable ones the centre's catalogue and the plugin
|
|
615
|
+
* manager know; no version is named here, because the catalogue and the install
|
|
616
|
+
* dialog are what discover the current one.
|
|
617
|
+
*/
|
|
618
|
+
/** The VibeDev components by package name; the centre is the way to all of them. */
|
|
619
|
+
const SUITE = {
|
|
620
|
+
account: "@vibedev-si/dsh-vibedev",
|
|
621
|
+
center: "@vibedev-si/dsh-ecosystem",
|
|
622
|
+
film: "dsh-film",
|
|
623
|
+
viewer: "@vibedev-si/dsh-media-viewer"
|
|
624
|
+
};
|
|
625
|
+
/** The package a person can add on the Plugins page when nothing can open the centre. */
|
|
626
|
+
const CENTER_SPEC = SUITE.center;
|
|
627
|
+
/** The main panel the VibeDev plugin centre registers its page under. */
|
|
628
|
+
const CENTER_PANEL = "vibedev-center";
|
|
629
|
+
/** A property value read as a record, without trusting its declared type. */
|
|
630
|
+
function asRecord(value) {
|
|
631
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* One service of the client context, read optionally.
|
|
635
|
+
* `ctx.get(name)` answers for an absent or inactive service without the strict property read;
|
|
636
|
+
* a Host, or a service, whose own getter throws answers undefined here rather than failing the plugin.
|
|
637
|
+
* @param ctx - the client context.
|
|
638
|
+
* @param name - the service name.
|
|
639
|
+
* @returns the service, or undefined when this Host does not serve it.
|
|
640
|
+
*/
|
|
641
|
+
function service(ctx, name) {
|
|
642
|
+
const record = asRecord(ctx);
|
|
643
|
+
if (record === void 0) return void 0;
|
|
644
|
+
const get = record.get;
|
|
645
|
+
if (typeof get === "function") try {
|
|
646
|
+
const value = get.call(record, name);
|
|
647
|
+
if (value !== void 0) return value;
|
|
648
|
+
} catch (_error) {}
|
|
649
|
+
try {
|
|
650
|
+
return record[name];
|
|
651
|
+
} catch (_error) {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Whether the Host has a bundle: the profile installed it, or the application provides it itself.
|
|
657
|
+
* @param bundle - one inventory row.
|
|
658
|
+
* @returns whether the component is usable without installing anything.
|
|
659
|
+
*/
|
|
660
|
+
function bundlePresent(bundle) {
|
|
661
|
+
return bundle.installed === true || bundle.removable === false && !bundle.source;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* The present package names of one inventory answer's rows.
|
|
665
|
+
* @param answer - the Host's answer to `listBundles`.
|
|
666
|
+
* @returns the names, empty when the answer carries no list.
|
|
667
|
+
*/
|
|
668
|
+
function presentFrom(answer) {
|
|
669
|
+
const rows = asRecord(answer)?.value;
|
|
670
|
+
if (!Array.isArray(rows)) return /* @__PURE__ */ new Set();
|
|
671
|
+
const names = /* @__PURE__ */ new Set();
|
|
672
|
+
for (const row of rows) {
|
|
673
|
+
const bundle = asRecord(row);
|
|
674
|
+
if (bundle === void 0 || !bundlePresent(bundle)) continue;
|
|
675
|
+
if (typeof bundle.name === "string" && bundle.name !== "") names.add(bundle.name);
|
|
676
|
+
}
|
|
677
|
+
return names;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* What one inventory answer means. Only an accepted envelope carrying a list is an inventory:
|
|
681
|
+
* a refused answer (`ok: false` with an error) and a value that is not a list are both unread,
|
|
682
|
+
* never an empty Host, so nothing is ever reported missing on the strength of a failure.
|
|
683
|
+
* A Host that answers with the list itself is accepted as well.
|
|
684
|
+
* @param answer - the Host's answer to `listBundles`.
|
|
685
|
+
* @returns the read, with `ok` false when nothing could be learned.
|
|
686
|
+
*/
|
|
687
|
+
function presentFromAnswer(answer) {
|
|
688
|
+
const record = asRecord(answer);
|
|
689
|
+
if (record === void 0) return {
|
|
690
|
+
ok: false,
|
|
691
|
+
present: /* @__PURE__ */ new Set()
|
|
692
|
+
};
|
|
693
|
+
if (record.ok === true && Array.isArray(record.value)) return {
|
|
694
|
+
ok: true,
|
|
695
|
+
present: presentFrom(record)
|
|
696
|
+
};
|
|
697
|
+
if (Array.isArray(answer)) return {
|
|
698
|
+
ok: true,
|
|
699
|
+
present: presentFrom({ value: answer })
|
|
700
|
+
};
|
|
701
|
+
return {
|
|
702
|
+
ok: false,
|
|
703
|
+
present: /* @__PURE__ */ new Set()
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Read the Host's plugin inventory once.
|
|
708
|
+
* @param ctx - the client context.
|
|
709
|
+
* @returns what is present, or `ok: false` when no inventory could be read.
|
|
710
|
+
*/
|
|
711
|
+
async function readPresence(ctx) {
|
|
712
|
+
const manager = asRecord(service(service(ctx, "remote"), "pluginManager"));
|
|
713
|
+
const listBundles = manager?.listBundles;
|
|
714
|
+
if (typeof listBundles !== "function") return {
|
|
715
|
+
ok: false,
|
|
716
|
+
present: /* @__PURE__ */ new Set()
|
|
717
|
+
};
|
|
718
|
+
try {
|
|
719
|
+
return presentFromAnswer(await listBundles.call(manager));
|
|
720
|
+
} catch (_error) {
|
|
721
|
+
return {
|
|
722
|
+
ok: false,
|
|
723
|
+
present: /* @__PURE__ */ new Set()
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Hear about installs, removals and activations.
|
|
729
|
+
* @param ctx - the client context.
|
|
730
|
+
* @param listener - called after every change.
|
|
731
|
+
* @returns the unsubscribe, a no-op on a Host without the event bus.
|
|
732
|
+
*/
|
|
733
|
+
function observePresence(ctx, listener) {
|
|
734
|
+
const remote = asRecord(service(ctx, "remote"));
|
|
735
|
+
const on = remote?.$on;
|
|
736
|
+
if (typeof on !== "function") return () => {};
|
|
737
|
+
try {
|
|
738
|
+
const off = on.call(remote, "plugin-manager/changed", listener);
|
|
739
|
+
if (typeof off !== "function") return () => {};
|
|
740
|
+
const stop = off;
|
|
741
|
+
return () => {
|
|
742
|
+
stop();
|
|
743
|
+
};
|
|
744
|
+
} catch (_error) {
|
|
745
|
+
return () => {};
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* The centre's main panel, where this Host registered one.
|
|
750
|
+
* A slots entry keeps its identity in `options` (`options.key` is the panel id the layout
|
|
751
|
+
* selects); older shapes kept `key`/`id` at the top level, which is still read as a fallback.
|
|
752
|
+
* @param ctx - the client context.
|
|
753
|
+
* @returns the panel id the layout selects, or undefined when there is no centre page.
|
|
754
|
+
*/
|
|
755
|
+
function centerPanel(ctx) {
|
|
756
|
+
const slots = asRecord(service(ctx, "slots"));
|
|
757
|
+
const entries = slots?.entries;
|
|
758
|
+
if (typeof entries !== "function") return void 0;
|
|
759
|
+
const listed = entries.call(slots, "main");
|
|
760
|
+
if (!Array.isArray(listed)) return void 0;
|
|
761
|
+
for (const entry of listed) {
|
|
762
|
+
const record = asRecord(entry);
|
|
763
|
+
const options = asRecord(record?.options);
|
|
764
|
+
if ((options?.key ?? record?.key ?? options?.id ?? record?.id) === CENTER_PANEL) return CENTER_PANEL;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Send the person to the VibeDev plugin centre, or to the Host's own way of installing it.
|
|
769
|
+
* A panel that is gone, a layout that refuses, and a missing navigation contract each fall
|
|
770
|
+
* through to the next, and the last answer is the spec to paste.
|
|
771
|
+
* @param ctx - the client context.
|
|
772
|
+
* @returns what was opened.
|
|
773
|
+
*/
|
|
774
|
+
function openCenter(ctx) {
|
|
775
|
+
const panel = centerPanel(ctx);
|
|
776
|
+
const selectPanel = asRecord(service(ctx, "layout"))?.selectPanel;
|
|
777
|
+
if (panel !== void 0 && typeof selectPanel === "function") try {
|
|
778
|
+
selectPanel.call(service(ctx, "layout"), panel);
|
|
779
|
+
return { kind: "center" };
|
|
780
|
+
} catch (_error) {}
|
|
781
|
+
const navigation = asRecord(service(ctx, "pluginNavigation"));
|
|
782
|
+
const openBundle = navigation?.openBundle;
|
|
783
|
+
if (typeof openBundle === "function") try {
|
|
784
|
+
openBundle.call(navigation, SUITE.center);
|
|
785
|
+
return { kind: "plugins" };
|
|
786
|
+
} catch (_error) {}
|
|
787
|
+
const openInstall = navigation?.openInstall;
|
|
788
|
+
if (typeof openInstall === "function") try {
|
|
789
|
+
openInstall.call(navigation, { spec: CENTER_SPEC });
|
|
790
|
+
return { kind: "plugins" };
|
|
791
|
+
} catch (_error) {}
|
|
792
|
+
return {
|
|
793
|
+
kind: "manual",
|
|
794
|
+
spec: CENTER_SPEC
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Which of `wanted` this Host does not have yet.
|
|
799
|
+
* @param present - what the Host has.
|
|
800
|
+
* @param wanted - the components, in the order the surface lists them.
|
|
801
|
+
* @returns the absent ones.
|
|
802
|
+
*/
|
|
803
|
+
function missingFrom(present, wanted) {
|
|
804
|
+
return wanted.filter((want) => !present.has(want.package));
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* What a surface says: the reasons something is absent, or the one way to the rest.
|
|
808
|
+
* An inventory that could not be read claims nothing and offers the centre.
|
|
809
|
+
* @param read - the inventory read.
|
|
810
|
+
* @param wanted - the components this surface leans on.
|
|
811
|
+
* @param translate - the entry's bound dictionary, so a card renders lines rather than keys.
|
|
812
|
+
* @returns the hint the card renders.
|
|
813
|
+
*/
|
|
814
|
+
function suiteHint(read, wanted, translate) {
|
|
815
|
+
if (!read.ok) return {
|
|
816
|
+
kind: "tools",
|
|
817
|
+
missing: []
|
|
818
|
+
};
|
|
819
|
+
const missing = missingFrom(read.present, wanted);
|
|
820
|
+
if (missing.length === 0) return {
|
|
821
|
+
kind: "tools",
|
|
822
|
+
missing: []
|
|
823
|
+
};
|
|
824
|
+
return {
|
|
825
|
+
kind: "missing",
|
|
826
|
+
missing: missing.map((want) => ({
|
|
827
|
+
package: want.package,
|
|
828
|
+
line: translate(want.key)
|
|
829
|
+
}))
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
/** Write text through the browser's clipboard, where the page has one. */
|
|
833
|
+
async function writeClipboard(text) {
|
|
834
|
+
const clipboard = asRecord(asRecord(globalThis)?.navigator)?.clipboard;
|
|
835
|
+
const writeText = asRecord(clipboard)?.writeText;
|
|
836
|
+
if (typeof writeText !== "function") return false;
|
|
837
|
+
try {
|
|
838
|
+
await writeText.call(clipboard, text);
|
|
839
|
+
return true;
|
|
840
|
+
} catch (_error) {
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* The entry's read of what this Host has, shared by every card it hands to the workbench.
|
|
846
|
+
* @param ctx - the client context.
|
|
847
|
+
* @param wanted - the components this plugin's own work leans on.
|
|
848
|
+
* @param translate - the entry's bound dictionary.
|
|
849
|
+
* @returns the store: subscribe and render through {@link SuiteView}, and start it inside an effect.
|
|
850
|
+
*/
|
|
851
|
+
function createSuiteStore(ctx, wanted, translate) {
|
|
852
|
+
let snapshot = {
|
|
853
|
+
read: false,
|
|
854
|
+
found: {
|
|
855
|
+
ok: false,
|
|
856
|
+
present: /* @__PURE__ */ new Set()
|
|
857
|
+
},
|
|
858
|
+
hint: {
|
|
859
|
+
kind: "tools",
|
|
860
|
+
missing: []
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
864
|
+
const publish = (next) => {
|
|
865
|
+
snapshot = next;
|
|
866
|
+
for (const listener of [...listeners]) listener();
|
|
867
|
+
};
|
|
868
|
+
const read = async () => {
|
|
869
|
+
const found = await readPresence(ctx);
|
|
870
|
+
publish({
|
|
871
|
+
read: true,
|
|
872
|
+
found,
|
|
873
|
+
hint: suiteHint(found, wanted, translate)
|
|
874
|
+
});
|
|
875
|
+
};
|
|
876
|
+
return {
|
|
877
|
+
subscribe(listener) {
|
|
878
|
+
listeners.add(listener);
|
|
879
|
+
return () => {
|
|
880
|
+
listeners.delete(listener);
|
|
881
|
+
};
|
|
882
|
+
},
|
|
883
|
+
getSnapshot: () => snapshot,
|
|
884
|
+
open: () => openCenter(ctx),
|
|
885
|
+
copy: writeClipboard,
|
|
886
|
+
start: () => {
|
|
887
|
+
read();
|
|
888
|
+
return observePresence(ctx, () => {
|
|
889
|
+
read();
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
//#endregion
|
|
579
895
|
//#region src/client/artwork.tsx
|
|
580
896
|
/**
|
|
581
897
|
* The script part: a page with lines.
|
|
@@ -795,7 +1111,7 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
|
|
|
795
1111
|
* @param props - the tab, the session and this part.
|
|
796
1112
|
* @returns the body.
|
|
797
1113
|
*/
|
|
798
|
-
function TabBody({ useTabInfo, sessionId, useSessions, translate: t, view }) {
|
|
1114
|
+
function TabBody({ useTabInfo, sessionId, useSessions, translate: t, view, suite }) {
|
|
799
1115
|
const { tab } = useTabInfo();
|
|
800
1116
|
const cwd = useSessions((sessions) => sessions.byId[sessionId]?.cwd);
|
|
801
1117
|
const [attempt, setAttempt] = (0, react.useState)(0);
|
|
@@ -825,7 +1141,8 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
|
|
|
825
1141
|
t,
|
|
826
1142
|
openView: (next) => {
|
|
827
1143
|
tab.actions.openTab(kindOf(next));
|
|
828
|
-
}
|
|
1144
|
+
},
|
|
1145
|
+
suite
|
|
829
1146
|
})
|
|
830
1147
|
})
|
|
831
1148
|
}, attempt)
|
|
@@ -846,6 +1163,18 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
|
|
|
846
1163
|
*/
|
|
847
1164
|
/** The dictionary namespace this plugin owns. */
|
|
848
1165
|
const LOCALE_NAMESPACE = "dsh-film";
|
|
1166
|
+
/**
|
|
1167
|
+
* The components this workbench's own work leans on: generating in the storyboard needs the
|
|
1168
|
+
* VibeDev account, and the results are watched in the media viewer. A Host that has both is
|
|
1169
|
+
* offered the centre instead, for everything else.
|
|
1170
|
+
*/
|
|
1171
|
+
const WANTED = [{
|
|
1172
|
+
package: SUITE.account,
|
|
1173
|
+
key: "suite.account.missing"
|
|
1174
|
+
}, {
|
|
1175
|
+
package: SUITE.viewer,
|
|
1176
|
+
key: "suite.viewer.missing"
|
|
1177
|
+
}];
|
|
849
1178
|
const name = "dsh-film";
|
|
850
1179
|
const inject = [
|
|
851
1180
|
"slots",
|
|
@@ -862,6 +1191,8 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
|
|
|
862
1191
|
en
|
|
863
1192
|
}), "dsh-film: dictionaries");
|
|
864
1193
|
const t = ctx.locale.bind(LOCALE_NAMESPACE);
|
|
1194
|
+
const suite = createSuiteStore(ctx, WANTED, t);
|
|
1195
|
+
ctx.effect(() => suite.start(), "dsh-film: suite presence");
|
|
865
1196
|
for (const part of PARTS) {
|
|
866
1197
|
ctx.effect(() => ctx.sidebarRightTabs.register({
|
|
867
1198
|
id: part.id,
|
|
@@ -882,7 +1213,8 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
|
|
|
882
1213
|
locale: LOCALE_NAMESPACE,
|
|
883
1214
|
inject: () => ({
|
|
884
1215
|
translate: t,
|
|
885
|
-
view: part.view
|
|
1216
|
+
view: part.view,
|
|
1217
|
+
suite
|
|
886
1218
|
})
|
|
887
1219
|
}, TabBody)), `dsh-film: ${part.kind} body`);
|
|
888
1220
|
}
|
|
@@ -178,7 +178,7 @@ window.__ModuleLoader__.load({ id: "dsh-film", chunk: "client.workbench.js", fac
|
|
|
178
178
|
}
|
|
179
179
|
//#endregion
|
|
180
180
|
//#region \0dsh-film-css:src/client/workbench/workbench.module.css.mjs
|
|
181
|
-
const css$1 = ".dshfilm_Z9L7vq_root{flex-direction:column;flex:auto;min-height:0;display:flex}.dshfilm_Z9L7vq_header{box-sizing:border-box;border-bottom:.5px solid var(--dsw-alias-border-l3);flex:none;align-items:center;gap:8px;height:38px;padding:0 12px 0 16px;display:flex}.dshfilm_Z9L7vq_titleHeading{min-width:0;max-width:60%;font-size:inherit;font-weight:inherit;flex:0 auto;align-items:center;margin:0;display:flex}.dshfilm_Z9L7vq_title{min-width:0;max-width:100%;height:26px;color:var(--dsw-alias-label-primary);font:inherit;font-size:var(--dsh-content-font-size-primary,14px);text-align:left;white-space:nowrap;text-overflow:ellipsis;cursor:text;background:0 0;border:0;border-radius:6px;flex:0 auto;margin:0 0 0 -6px;padding:0 6px;font-weight:600;overflow:hidden}.dshfilm_Z9L7vq_title:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.dshfilm_Z9L7vq_title:disabled{color:var(--dsw-alias-label-secondary);cursor:default}.dshfilm_Z9L7vq_titleInput{flex:0 260px;width:260px;min-width:120px;margin-left:-6px}.dshfilm_Z9L7vq_aspect{font-variant-numeric:tabular-nums;flex:none}.dshfilm_Z9L7vq_headerError{color:var(--dsw-alias-state-error-primary);white-space:nowrap;text-overflow:ellipsis;margin-left:auto;font-size:12px;overflow:hidden}.dshfilm_Z9L7vq_file{color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-overflow:ellipsis;margin-left:auto;font-size:12px;overflow:hidden}.dshfilm_Z9L7vq_body{scrollbar-gutter:stable;flex:auto;min-height:0;padding:12px 16px 16px;overflow:auto}.dshfilm_Z9L7vq_frameBody{flex-direction:column;flex:auto;min-height:0;display:flex;overflow:hidden}.dshfilm_Z9L7vq_appFrame{background:var(--dsw-alias-bg-base);border:0;flex:auto;width:100%;min-height:0}.dshfilm_Z9L7vq_restartBanner{border-bottom:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-state-warn-tertiary,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-primary));flex:none;margin:0;padding:8px 16px;font-size:13px;line-height:1.5}.dshfilm_Z9L7vq_notice{color:var(--dsw-alias-label-secondary);flex-direction:column;align-items:flex-start;gap:12px;margin:0;padding:16px;display:flex}.dshfilm_Z9L7vq_notice p{margin:0}.dshfilm_Z9L7vq_detail{color:var(--dsw-alias-label-tertiary);word-break:break-word;font-size:12px}.dshfilm_Z9L7vq_soon{color:var(--dsw-alias-label-tertiary);margin:0}";
|
|
181
|
+
const css$1 = ".dshfilm_Z9L7vq_root{flex-direction:column;flex:auto;min-height:0;display:flex}.dshfilm_Z9L7vq_header{box-sizing:border-box;border-bottom:.5px solid var(--dsw-alias-border-l3);flex:none;align-items:center;gap:8px;height:38px;padding:0 12px 0 16px;display:flex}.dshfilm_Z9L7vq_titleHeading{min-width:0;max-width:60%;font-size:inherit;font-weight:inherit;flex:0 auto;align-items:center;margin:0;display:flex}.dshfilm_Z9L7vq_title{min-width:0;max-width:100%;height:26px;color:var(--dsw-alias-label-primary);font:inherit;font-size:var(--dsh-content-font-size-primary,14px);text-align:left;white-space:nowrap;text-overflow:ellipsis;cursor:text;background:0 0;border:0;border-radius:6px;flex:0 auto;margin:0 0 0 -6px;padding:0 6px;font-weight:600;overflow:hidden}.dshfilm_Z9L7vq_title:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.dshfilm_Z9L7vq_title:disabled{color:var(--dsw-alias-label-secondary);cursor:default}.dshfilm_Z9L7vq_titleInput{flex:0 260px;width:260px;min-width:120px;margin-left:-6px}.dshfilm_Z9L7vq_aspect{font-variant-numeric:tabular-nums;flex:none}.dshfilm_Z9L7vq_headerError{color:var(--dsw-alias-state-error-primary);white-space:nowrap;text-overflow:ellipsis;margin-left:auto;font-size:12px;overflow:hidden}.dshfilm_Z9L7vq_file{color:var(--dsw-alias-label-tertiary);white-space:nowrap;text-overflow:ellipsis;margin-left:auto;font-size:12px;overflow:hidden}.dshfilm_Z9L7vq_body{scrollbar-gutter:stable;flex:auto;min-height:0;padding:12px 16px 16px;overflow:auto}.dshfilm_Z9L7vq_frameBody{flex-direction:column;flex:auto;min-height:0;display:flex;overflow:hidden}.dshfilm_Z9L7vq_appFrame{background:var(--dsw-alias-bg-base);border:0;flex:auto;width:100%;min-height:0}.dshfilm_Z9L7vq_restartBanner{border-bottom:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-state-warn-tertiary,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-state-warn-label,var(--dsw-alias-label-primary));flex:none;margin:0;padding:8px 16px;font-size:13px;line-height:1.5}.dshfilm_Z9L7vq_notice{color:var(--dsw-alias-label-secondary);flex-direction:column;align-items:flex-start;gap:12px;margin:0;padding:16px;display:flex}.dshfilm_Z9L7vq_notice p{margin:0}.dshfilm_Z9L7vq_detail{color:var(--dsw-alias-label-tertiary);word-break:break-word;font-size:12px}.dshfilm_Z9L7vq_soon{color:var(--dsw-alias-label-tertiary);margin:0}.dshfilm_Z9L7vq_suite{border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-l2,transparent);border-radius:10px;flex-direction:column;flex:none;gap:4px;margin:8px 12px 12px;padding:10px 12px;display:flex}.dshfilm_Z9L7vq_suiteMissing{border-color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-border-l2))}.dshfilm_Z9L7vq_suiteTitle{color:var(--dsw-alias-label-primary);margin:0;font-size:13px;font-weight:500}.dshfilm_Z9L7vq_suiteLine{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.6}.dshfilm_Z9L7vq_suiteActions{flex-wrap:wrap;gap:8px;padding-top:2px;display:flex}.dshfilm_Z9L7vq_suiteManual{flex-wrap:wrap;align-items:center;gap:6px;padding-top:2px;display:flex}.dshfilm_Z9L7vq_suiteSpec{background:var(--dsw-alias-bg-l1,transparent);color:var(--dsw-alias-label-primary);user-select:text;border-radius:6px;padding:2px 6px;font-size:12px}";
|
|
182
182
|
const tagId$1 = "dsh-film/workbench.module.css";
|
|
183
183
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
184
184
|
const tag = document.createElement("style");
|
|
@@ -200,6 +200,13 @@ window.__ModuleLoader__.load({ id: "dsh-film", chunk: "client.workbench.js", fac
|
|
|
200
200
|
"restartBanner": "dshfilm_Z9L7vq_restartBanner",
|
|
201
201
|
"root": "dshfilm_Z9L7vq_root",
|
|
202
202
|
"soon": "dshfilm_Z9L7vq_soon",
|
|
203
|
+
"suite": "dshfilm_Z9L7vq_suite",
|
|
204
|
+
"suiteActions": "dshfilm_Z9L7vq_suiteActions",
|
|
205
|
+
"suiteLine": "dshfilm_Z9L7vq_suiteLine",
|
|
206
|
+
"suiteManual": "dshfilm_Z9L7vq_suiteManual",
|
|
207
|
+
"suiteMissing": "dshfilm_Z9L7vq_suiteMissing",
|
|
208
|
+
"suiteSpec": "dshfilm_Z9L7vq_suiteSpec",
|
|
209
|
+
"suiteTitle": "dshfilm_Z9L7vq_suiteTitle",
|
|
203
210
|
"title": "dshfilm_Z9L7vq_title",
|
|
204
211
|
"titleHeading": "dshfilm_Z9L7vq_titleHeading",
|
|
205
212
|
"titleInput": "dshfilm_Z9L7vq_titleInput"
|
|
@@ -693,7 +700,7 @@ window.__ModuleLoader__.load({ id: "dsh-film", chunk: "client.workbench.js", fac
|
|
|
693
700
|
* an older dictionary, whose translate function echoes keys it lacks.
|
|
694
701
|
*/
|
|
695
702
|
/** The version this bundle was built as (tsdown defines it); `dev` where nothing does (node tests). */
|
|
696
|
-
const CLIENT_VERSION = "0.3.
|
|
703
|
+
const CLIENT_VERSION = "0.3.2";
|
|
697
704
|
/**
|
|
698
705
|
* Whether the page should ask for a restart.
|
|
699
706
|
* @param client - the version this page was built as.
|
|
@@ -5828,6 +5835,104 @@ window.__ModuleLoader__.load({ id: "dsh-film", chunk: "client.workbench.js", fac
|
|
|
5828
5835
|
});
|
|
5829
5836
|
}
|
|
5830
5837
|
//#endregion
|
|
5838
|
+
//#region src/client/workbench/SuiteCard.tsx
|
|
5839
|
+
/**
|
|
5840
|
+
* The footer of a workbench part: what this Host still lacks for the work this part does, or,
|
|
5841
|
+
* when nothing is missing, one quiet way into the plugin centre for everything else.
|
|
5842
|
+
*
|
|
5843
|
+
* It is a plain card at the end of the part, drawn from the entry's own read of the Host
|
|
5844
|
+
* (passed in as {@link SuiteView}); nothing here installs, navigates on its own, or appears
|
|
5845
|
+
* before the read settled. Opening the centre is the person's click, and a Host where nothing
|
|
5846
|
+
* can open shows the package spec instead, to copy into the Plugins page.
|
|
5847
|
+
*/
|
|
5848
|
+
/**
|
|
5849
|
+
* Draw the card from a settled read.
|
|
5850
|
+
* @param props - the translated hint, the manual fallback and the two clicks.
|
|
5851
|
+
* @returns the card.
|
|
5852
|
+
*/
|
|
5853
|
+
function suiteCardView({ t, hint, manual, copied, onOpen, onCopy }) {
|
|
5854
|
+
const missing = hint.kind === "missing";
|
|
5855
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5856
|
+
className: `${workbench_module_css_default.suite} ${missing ? workbench_module_css_default.suiteMissing : ""}`,
|
|
5857
|
+
"data-dsh-film-suite": hint.kind,
|
|
5858
|
+
"aria-label": t(missing ? "suite.missing.title" : "suite.tools.title"),
|
|
5859
|
+
children: [
|
|
5860
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5861
|
+
className: workbench_module_css_default.suiteTitle,
|
|
5862
|
+
children: t(missing ? "suite.missing.title" : "suite.tools.title")
|
|
5863
|
+
}),
|
|
5864
|
+
hint.missing.map((want) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5865
|
+
className: workbench_module_css_default.suiteLine,
|
|
5866
|
+
children: want.line
|
|
5867
|
+
}, want.package)),
|
|
5868
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5869
|
+
className: workbench_module_css_default.suiteLine,
|
|
5870
|
+
children: t(missing ? "suite.missing.keep" : "suite.tools.hint")
|
|
5871
|
+
}),
|
|
5872
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5873
|
+
className: workbench_module_css_default.suiteActions,
|
|
5874
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
5875
|
+
variant: "outline",
|
|
5876
|
+
size: "sm",
|
|
5877
|
+
onClick: onOpen,
|
|
5878
|
+
children: t("suite.open")
|
|
5879
|
+
})
|
|
5880
|
+
}),
|
|
5881
|
+
manual === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5882
|
+
className: workbench_module_css_default.suiteManual,
|
|
5883
|
+
children: [
|
|
5884
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5885
|
+
className: workbench_module_css_default.suiteLine,
|
|
5886
|
+
children: t("suite.manual")
|
|
5887
|
+
}),
|
|
5888
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
5889
|
+
className: workbench_module_css_default.suiteSpec,
|
|
5890
|
+
children: manual.spec
|
|
5891
|
+
}),
|
|
5892
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
5893
|
+
variant: "ghost",
|
|
5894
|
+
size: "sm",
|
|
5895
|
+
onClick: () => {
|
|
5896
|
+
onCopy(manual.spec);
|
|
5897
|
+
},
|
|
5898
|
+
children: t(copied ? "suite.copied" : "suite.copy")
|
|
5899
|
+
})
|
|
5900
|
+
]
|
|
5901
|
+
})
|
|
5902
|
+
]
|
|
5903
|
+
});
|
|
5904
|
+
}
|
|
5905
|
+
/**
|
|
5906
|
+
* Draw the card.
|
|
5907
|
+
* @param props - the entry's suite view and this plugin's dictionary.
|
|
5908
|
+
* @returns the card, or nothing before the first read settled.
|
|
5909
|
+
*/
|
|
5910
|
+
function SuiteCard({ suite, t }) {
|
|
5911
|
+
const { read, hint } = (0, react.useSyncExternalStore)(suite.subscribe, suite.getSnapshot, suite.getSnapshot);
|
|
5912
|
+
/** Set once a click found nothing to open: the spec to copy is then on screen. */
|
|
5913
|
+
const [manual, setManual] = (0, react.useState)(void 0);
|
|
5914
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
5915
|
+
if (!read) return null;
|
|
5916
|
+
const open = () => {
|
|
5917
|
+
const target = suite.open();
|
|
5918
|
+
setCopied(false);
|
|
5919
|
+
setManual(target.kind === "manual" ? { spec: target.spec } : void 0);
|
|
5920
|
+
};
|
|
5921
|
+
const copy = (spec) => {
|
|
5922
|
+
suite.copy(spec).then((written) => {
|
|
5923
|
+
setCopied(written);
|
|
5924
|
+
});
|
|
5925
|
+
};
|
|
5926
|
+
return suiteCardView({
|
|
5927
|
+
t,
|
|
5928
|
+
hint,
|
|
5929
|
+
manual,
|
|
5930
|
+
copied,
|
|
5931
|
+
onOpen: open,
|
|
5932
|
+
onCopy: copy
|
|
5933
|
+
});
|
|
5934
|
+
}
|
|
5935
|
+
//#endregion
|
|
5831
5936
|
//#region src/client/workbench/workbench.tsx
|
|
5832
5937
|
/**
|
|
5833
5938
|
* The film workbench, loaded on first use as client/client.workbench.js.
|
|
@@ -5888,7 +5993,7 @@ window.__ModuleLoader__.load({ id: "dsh-film", chunk: "client.workbench.js", fac
|
|
|
5888
5993
|
});
|
|
5889
5994
|
}
|
|
5890
5995
|
/** A part this build draws, under the restart banner. */
|
|
5891
|
-
function ProjectPart({ view, cwd, visible, t, openView, banner, notice }) {
|
|
5996
|
+
function ProjectPart({ view, cwd, visible, t, openView, suite, banner, notice }) {
|
|
5892
5997
|
const { state, reload } = useProject(cwd, visible);
|
|
5893
5998
|
if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5894
5999
|
className: workbench_module_css_default.root,
|
|
@@ -5937,6 +6042,7 @@ window.__ModuleLoader__.load({ id: "dsh-film", chunk: "client.workbench.js", fac
|
|
|
5937
6042
|
visible,
|
|
5938
6043
|
t,
|
|
5939
6044
|
openView,
|
|
6045
|
+
suite,
|
|
5940
6046
|
project: state.project
|
|
5941
6047
|
});
|
|
5942
6048
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -5964,6 +6070,10 @@ window.__ModuleLoader__.load({ id: "dsh-film", chunk: "client.workbench.js", fac
|
|
|
5964
6070
|
}),
|
|
5965
6071
|
blocked: notice === null ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: restartText(t, notice) })
|
|
5966
6072
|
}, `${view}:${state.project.id}`)
|
|
6073
|
+
}),
|
|
6074
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SuiteCard, {
|
|
6075
|
+
suite,
|
|
6076
|
+
t
|
|
5967
6077
|
})
|
|
5968
6078
|
]
|
|
5969
6079
|
});
|
|
@@ -75,8 +75,8 @@ export function mediaTaskTools(services) {
|
|
|
75
75
|
description: 'Read one film task by its taskId — a storyboard generation of this workbench (the id canvas_get_generation_status reports as '
|
|
76
76
|
+ 'outputs[].task), or a cut, join or sound copy a video_* tool started: status, the last progress line, the error (with reasons when the Host could '
|
|
77
77
|
+ 'not copy a file), and its file. A finished edit\'s file.landedNodeId is the node it put on the board (file.landError says why it could not), and '
|
|
78
|
-
+ 'file.derivedFrom lists its sources with the range of each it holds (atMs: where that range starts in the result). Film tasks only —
|
|
79
|
-
+ 'media_tasks lists a different kind of task.',
|
|
78
|
+
+ 'file.derivedFrom lists its sources with the range of each it holds (atMs: where that range starts in the result). Film tasks only — the VibeDev plugin\'s '
|
|
79
|
+
+ '(dsh-vibedev, formerly dsh-media) media_tasks lists a different kind of task.',
|
|
80
80
|
parameters: {
|
|
81
81
|
taskId: { type: 'string', required: true },
|
|
82
82
|
},
|
|
@@ -91,7 +91,7 @@ export function mediaTaskTools(services) {
|
|
|
91
91
|
name: 'media_cancel_task',
|
|
92
92
|
description: 'Cancel one film task by its taskId (a storyboard generation of this workbench, as canvas_get_generation_status reports it in '
|
|
93
93
|
+ 'outputs[].task, or an edit a video_* tool started); a finished task is left as it is. A cancelled edit leaves no partial file; one whose result '
|
|
94
|
-
+ 'file is already written finishes as done. Film tasks only, not
|
|
94
|
+
+ 'file is already written finishes as done. Film tasks only, not the VibeDev plugin\'s media_tasks.',
|
|
95
95
|
parameters: {
|
|
96
96
|
taskId: { type: 'string', required: true },
|
|
97
97
|
},
|
package/lib/media/tasks.js
CHANGED
|
@@ -159,7 +159,7 @@ function sizeFor(aspect) {
|
|
|
159
159
|
* dsh-media's own messages are written for the agent.
|
|
160
160
|
*/
|
|
161
161
|
const USER_MESSAGES = {
|
|
162
|
-
NOT_SIGNED_IN: '生成需要登录 VibeDev
|
|
162
|
+
NOT_SIGNED_IN: '生成需要登录 VibeDev 账号:在侧边栏底部「登录 VibeDev」或「设置 → VibeDev 账号」中登录。在 VibeDev 应用里会直接使用应用登录的账号。',
|
|
163
163
|
INSUFFICIENT_BALANCE: 'VibeDev 余额不足,充值后再试。',
|
|
164
164
|
SPENDING_DECLINED: '已取消,没有扣费。',
|
|
165
165
|
ABORTED: '已取消。',
|
|
@@ -282,7 +282,7 @@ export class FilmMediaTasks {
|
|
|
282
282
|
service() {
|
|
283
283
|
const media = this.media();
|
|
284
284
|
if (media === undefined)
|
|
285
|
-
throw new FilmMediaError(503, 'MEDIA_SERVICE_UNAVAILABLE', '
|
|
285
|
+
throw new FilmMediaError(503, 'MEDIA_SERVICE_UNAVAILABLE', '生成需要「VibeDev 账号与模型」插件(@vibedev-si/dsh-vibedev,原 dsh-media)。请在插件页安装并启用它。');
|
|
286
286
|
return media;
|
|
287
287
|
}
|
|
288
288
|
/**
|
|
@@ -22,14 +22,14 @@ export function addMediaRoutes(router, tasks, media) {
|
|
|
22
22
|
router.add('GET', '/api/media/models', async (request) => {
|
|
23
23
|
const service = media();
|
|
24
24
|
if (service === undefined) {
|
|
25
|
-
return { ...canvasCatalogue([]), providers: [{ id: GATEWAY_PROVIDER, label: 'VibeDev', integrated: false, configured: false, hint: '
|
|
25
|
+
return { ...canvasCatalogue([]), providers: [{ id: GATEWAY_PROVIDER, label: 'VibeDev', integrated: false, configured: false, hint: '生成需要「VibeDev 账号与模型」插件(@vibedev-si/dsh-vibedev,原 dsh-media)。' }] };
|
|
26
26
|
}
|
|
27
27
|
try {
|
|
28
28
|
return canvasCatalogue(await service.models(request.raw.signal));
|
|
29
29
|
}
|
|
30
30
|
catch (error) {
|
|
31
31
|
const code = error?.code;
|
|
32
|
-
const hint = code === 'NOT_SIGNED_IN' ? '请先登录 VibeDev
|
|
32
|
+
const hint = code === 'NOT_SIGNED_IN' ? '请先登录 VibeDev 账号(侧边栏底部「登录 VibeDev」或「设置 → VibeDev 账号」)。' : `暂时读不到模型目录:${error instanceof Error ? error.message : String(error)}`;
|
|
33
33
|
return { ...canvasCatalogue([]), providers: [{ id: GATEWAY_PROVIDER, label: 'VibeDev', integrated: true, configured: false, hint }] };
|
|
34
34
|
}
|
|
35
35
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-film",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "VibeDev 影视工作台:在 DeepSeek Harness 和 VibeDev 的右侧侧栏里写剧本、排分镜和调度导演台,Agent 用影视工具协同改片 · Film workbench for DeepSeek Harness and VibeDev: screenplay, storyboard canvas and director desk in the right sidebar, with film tools for the agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"storyboard",
|
|
54
54
|
"ai-agent"
|
|
55
55
|
],
|
|
56
|
-
"homepage": "https://github.com/
|
|
56
|
+
"homepage": "https://github.com/VibeDev-Si/dsh-film#readme",
|
|
57
57
|
"repository": {
|
|
58
58
|
"type": "git",
|
|
59
|
-
"url": "git+https://github.com/
|
|
59
|
+
"url": "git+https://github.com/VibeDev-Si/dsh-film.git"
|
|
60
60
|
},
|
|
61
61
|
"bugs": {
|
|
62
|
-
"url": "https://github.com/
|
|
62
|
+
"url": "https://github.com/VibeDev-Si/dsh-film/issues"
|
|
63
63
|
},
|
|
64
64
|
"license": "MIT",
|
|
65
65
|
"engines": {
|