dsh-film 0.3.1 → 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 +17 -3
- package/client/client.js +335 -3
- package/client/client.workbench.js +113 -3
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -37,14 +37,14 @@ 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
|
|
|
@@ -56,13 +56,19 @@ 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
|
+
|
|
66
72
|
### 0.3.1 版本说明 · 0.3.1 release note
|
|
67
73
|
|
|
68
74
|
- **配合改名后的「VibeDev 账号与模型」插件**([@vibedev-si/dsh-vibedev](https://github.com/VibeDev-Si/dsh-vibedev),原 dsh-media):未登录时的提示改为指向侧边栏底部「登录 VibeDev」和「设置 → VibeDev 账号」(dsh-media 的设置页里并没有登录入口);缺少插件时的提示写明新的插件名和包名;Agent 工具说明里的 dsh-media 改称 VibeDev 插件。功能没有变化:分镜画布照旧通过 `vibedevMedia` 服务生成,新旧两个插件都提供这个服务。
|
|
@@ -225,6 +231,14 @@ npm run build # 先删掉 lib/ 和 client/,再构建 lib/(Host 一侧
|
|
|
225
231
|
|
|
226
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.
|
|
227
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
|
+
|
|
228
242
|
## 许可 · License
|
|
229
243
|
|
|
230
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
|
});
|
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": {
|