dsh-film 0.3.2 → 0.3.3

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 CHANGED
@@ -37,7 +37,7 @@ 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/VibeDev-Si/dsh-film/releases/download/v0.3.2/dsh-film-0.3.2.tgz
40
+ https://github.com/VibeDev-Si/dsh-film/releases/download/v0.3.3/dsh-film-0.3.3.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`。
@@ -56,18 +56,18 @@ 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/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`.
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.3/dsh-film-0.3.3.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
66
+ ### 0.3.3 版本说明 · 0.3.3 release note
67
67
 
68
- - **新增「相关组件」入口**:剧本、分镜画布和导演台三个标签的尾部多了一张卡片,按宿主真实的安装情况,缺哪个 VibeDev 组件就逐条说明它管什么——不装「[VibeDev 账号与模型](https://github.com/VibeDev-Si/dsh-vibedev)」就生成不了图片和视频,不装「媒体预览与画廊」就不能在右侧栏直接播生成的结果;点一下打开 VibeDev 插件中心(宿主没有中心页面时退回它的安装对话框,再不然给出可以复制的包名),不会自动安装。宿主已经安装或应用自己内置的组件不会重复推荐,两件都有时只给「更多创作工具」这一个入口。升级插件不动工作区 `film/` 下的作品文件,也不影响 VibeDev 账号登录。
68
+ - **撤回了 0.3.2 的「相关组件」入口**:上一版加在剧本、分镜画布和导演台三个标签页尾的那张卡片(插件之间的推荐位)影响页面观感,已经整个去掉;怎么引荐、在哪引荐等重新设计之后再考虑。工作区 `film/` 里的作品和 VibeDev 账号登录都不受影响,其它功能与 0.3.1 一致。
69
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.
70
+ - **The 相关组件 (related components) entry from 0.3.2 is withdrawn**: the card 0.3.2 added to the end of the script, storyboard and director desk tabs — a recommendation slot between plugins — hurt how the pages looked, so it is gone entirely; how and where to point people at other plugins will be considered again after a redesign. The work in your workspace's `film/` folder and your VibeDev sign-in are unaffected, and everything else matches 0.3.1.
71
71
 
72
72
  ### 0.3.1 版本说明 · 0.3.1 release note
73
73
 
package/client/client.js CHANGED
@@ -22,16 +22,6 @@ 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": "已复制",
35
25
  "story.title": "剧本",
36
26
  "story.guide.description": "分场大纲、角色和版本,写好交给分镜画布",
37
27
  "board.title": "分镜画布",
@@ -314,16 +304,6 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
314
304
  "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.",
315
305
  "runtime.unknown": "an unknown version",
316
306
  "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",
327
307
  "story.title": "Script",
328
308
  "story.guide.description": "Scene outline, characters and versions, handed to the storyboard when ready",
329
309
  "board.title": "Storyboard",
@@ -596,302 +576,6 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
596
576
  "sw.impact.locate": "Locate on canvas"
597
577
  };
598
578
  //#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
895
579
  //#region src/client/artwork.tsx
896
580
  /**
897
581
  * The script part: a page with lines.
@@ -1111,7 +795,7 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
1111
795
  * @param props - the tab, the session and this part.
1112
796
  * @returns the body.
1113
797
  */
1114
- function TabBody({ useTabInfo, sessionId, useSessions, translate: t, view, suite }) {
798
+ function TabBody({ useTabInfo, sessionId, useSessions, translate: t, view }) {
1115
799
  const { tab } = useTabInfo();
1116
800
  const cwd = useSessions((sessions) => sessions.byId[sessionId]?.cwd);
1117
801
  const [attempt, setAttempt] = (0, react.useState)(0);
@@ -1141,8 +825,7 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
1141
825
  t,
1142
826
  openView: (next) => {
1143
827
  tab.actions.openTab(kindOf(next));
1144
- },
1145
- suite
828
+ }
1146
829
  })
1147
830
  })
1148
831
  }, attempt)
@@ -1163,18 +846,6 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
1163
846
  */
1164
847
  /** The dictionary namespace this plugin owns. */
1165
848
  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
- }];
1178
849
  const name = "dsh-film";
1179
850
  const inject = [
1180
851
  "slots",
@@ -1191,8 +862,6 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
1191
862
  en
1192
863
  }), "dsh-film: dictionaries");
1193
864
  const t = ctx.locale.bind(LOCALE_NAMESPACE);
1194
- const suite = createSuiteStore(ctx, WANTED, t);
1195
- ctx.effect(() => suite.start(), "dsh-film: suite presence");
1196
865
  for (const part of PARTS) {
1197
866
  ctx.effect(() => ctx.sidebarRightTabs.register({
1198
867
  id: part.id,
@@ -1213,8 +882,7 @@ window.__ModuleLoader__.load({ id: "dsh-film", factory: (require) => {
1213
882
  locale: LOCALE_NAMESPACE,
1214
883
  inject: () => ({
1215
884
  translate: t,
1216
- view: part.view,
1217
- suite
885
+ view: part.view
1218
886
  })
1219
887
  }, TabBody)), `dsh-film: ${part.kind} body`);
1220
888
  }
@@ -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}.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}";
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}";
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,13 +200,6 @@ 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",
210
203
  "title": "dshfilm_Z9L7vq_title",
211
204
  "titleHeading": "dshfilm_Z9L7vq_titleHeading",
212
205
  "titleInput": "dshfilm_Z9L7vq_titleInput"
@@ -700,7 +693,7 @@ window.__ModuleLoader__.load({ id: "dsh-film", chunk: "client.workbench.js", fac
700
693
  * an older dictionary, whose translate function echoes keys it lacks.
701
694
  */
702
695
  /** The version this bundle was built as (tsdown defines it); `dev` where nothing does (node tests). */
703
- const CLIENT_VERSION = "0.3.2";
696
+ const CLIENT_VERSION = "0.3.3";
704
697
  /**
705
698
  * Whether the page should ask for a restart.
706
699
  * @param client - the version this page was built as.
@@ -5835,104 +5828,6 @@ window.__ModuleLoader__.load({ id: "dsh-film", chunk: "client.workbench.js", fac
5835
5828
  });
5836
5829
  }
5837
5830
  //#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
5936
5831
  //#region src/client/workbench/workbench.tsx
5937
5832
  /**
5938
5833
  * The film workbench, loaded on first use as client/client.workbench.js.
@@ -5993,7 +5888,7 @@ window.__ModuleLoader__.load({ id: "dsh-film", chunk: "client.workbench.js", fac
5993
5888
  });
5994
5889
  }
5995
5890
  /** A part this build draws, under the restart banner. */
5996
- function ProjectPart({ view, cwd, visible, t, openView, suite, banner, notice }) {
5891
+ function ProjectPart({ view, cwd, visible, t, openView, banner, notice }) {
5997
5892
  const { state, reload } = useProject(cwd, visible);
5998
5893
  if (state.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5999
5894
  className: workbench_module_css_default.root,
@@ -6042,7 +5937,6 @@ window.__ModuleLoader__.load({ id: "dsh-film", chunk: "client.workbench.js", fac
6042
5937
  visible,
6043
5938
  t,
6044
5939
  openView,
6045
- suite,
6046
5940
  project: state.project
6047
5941
  });
6048
5942
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -6070,10 +5964,6 @@ window.__ModuleLoader__.load({ id: "dsh-film", chunk: "client.workbench.js", fac
6070
5964
  }),
6071
5965
  blocked: notice === null ? void 0 : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: restartText(t, notice) })
6072
5966
  }, `${view}:${state.project.id}`)
6073
- }),
6074
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SuiteCard, {
6075
- suite,
6076
- t
6077
5967
  })
6078
5968
  ]
6079
5969
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-film",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
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",