@ninkch/dsh-infinite-canvas 0.16.0 → 0.17.0
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 +8 -0
- package/README.zh-CN.md +8 -0
- package/client.js +99 -1
- package/index.js +68 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,6 +64,14 @@ Text models come from Harness itself (no extra keys). Image, video and TTS backe
|
|
|
64
64
|
|
|
65
65
|
When the companion [`dsh-artcraft`](../dsh-artcraft/README.md) plugin is installed, the canvas talks to the local **PhotoCraft / FilmCraft / EffectCraft** toolchain over same-origin HTTP: assets sync to a per-project data folder, images get headless PhotoCraft grading (single or batch), and final cuts export as FCP7 XML + SRT for FilmCraft to render as MP4. Status lights, graceful degradation and a full contract live in [`docs/artcraft-handoff.md`](./docs/artcraft-handoff.md).
|
|
66
66
|
|
|
67
|
+
## Publishing (maintainers)
|
|
68
|
+
|
|
69
|
+
Publishing runs through **npm Trusted Publishing** (OIDC) — no npm token exists anywhere:
|
|
70
|
+
|
|
71
|
+
- **Release flow**: create a GitHub Release (tag `v0.17.0`, …) → Actions publishes that version automatically.
|
|
72
|
+
- **Manual flow**: repo → Actions → *publish* → Run workflow → type a version.
|
|
73
|
+
- The `publish.yml` workflow resolves the version from the release tag (or your input) and publishes with a `provenance` badge.
|
|
74
|
+
|
|
67
75
|
## Data & privacy
|
|
68
76
|
|
|
69
77
|
- Canvas documents, projects, settings and the series bible live in browser `localStorage` (`dsh-ic:*`); assets live in IndexedDB (`dsh-infinite-canvas`).
|
package/README.zh-CN.md
CHANGED
|
@@ -64,6 +64,14 @@ flowchart LR
|
|
|
64
64
|
|
|
65
65
|
安装配套的 [`dsh-artcraft`](../dsh-artcraft/README.md) 插件后,画布通过同源 HTTP 调用本地 **PhotoCraft / FilmCraft / EffectCraft** 工具链:资产同步到按项目隔离的数据目录,图片走 PhotoCraft 无头调色(单张或批量),成片导出 FCP7 XML + SRT 交给 FilmCraft 渲染 MP4。状态灯、优雅降级与完整契约见 [`docs/artcraft-handoff.md`](./docs/artcraft-handoff.md)。
|
|
66
66
|
|
|
67
|
+
## 发布(维护者)
|
|
68
|
+
|
|
69
|
+
发布走 **npm Trusted Publishing**(OIDC)——不依赖任何 npm 令牌:
|
|
70
|
+
|
|
71
|
+
- **Release 流程**:GitHub 仓库创建 Release(tag `v0.17.0`…)→ Actions 自动发布该版本。
|
|
72
|
+
- **手动流程**:仓库 → Actions → *publish* → Run workflow → 输入版本号。
|
|
73
|
+
- `publish.yml` 从 release tag(或手动输入)解析版本号,发布时带 `provenance` 徽标。
|
|
74
|
+
|
|
67
75
|
## 数据与隐私
|
|
68
76
|
|
|
69
77
|
- 画布文档、项目、设置与设定集存于浏览器 `localStorage`(`dsh-ic:*`);资产存于 IndexedDB(`dsh-infinite-canvas`)。
|
package/client.js
CHANGED
|
@@ -329,6 +329,11 @@ window.__ModuleLoader__.load({
|
|
|
329
329
|
'ttsCategory': '配音',
|
|
330
330
|
'audioKind': '音频',
|
|
331
331
|
'finalcutCategory': '成片',
|
|
332
|
+
'updateBtn': '检查更新',
|
|
333
|
+
'updateAvailable': '新版本可用',
|
|
334
|
+
'updateNow': '一键更新',
|
|
335
|
+
'updateUpToDate': '已是最新版本',
|
|
336
|
+
'releaseNotes': '发布说明',
|
|
332
337
|
'history': '历史版本',
|
|
333
338
|
'refList': '参考图([图N] 按此顺序绑定进提示词)',
|
|
334
339
|
'moveUp': '上移',
|
|
@@ -577,6 +582,11 @@ window.__ModuleLoader__.load({
|
|
|
577
582
|
'ttsCategory': 'Voice',
|
|
578
583
|
'audioKind': 'Audio',
|
|
579
584
|
'finalcutCategory': 'Final cut',
|
|
585
|
+
'updateBtn': 'Check updates',
|
|
586
|
+
'updateAvailable': 'New version available',
|
|
587
|
+
'updateNow': 'Update now',
|
|
588
|
+
'updateUpToDate': 'Up to date',
|
|
589
|
+
'releaseNotes': 'release notes',
|
|
580
590
|
'history': 'History',
|
|
581
591
|
'refList': 'Refs ([图N] bound to prompt in this order)',
|
|
582
592
|
'moveUp': 'Move up',
|
|
@@ -1814,6 +1824,41 @@ window.__ModuleLoader__.load({
|
|
|
1814
1824
|
// 3) craft.file(outRel) 回收入库;预留 file.exportLottie 导出按钮位(Lottie 可在画布网页内预览)
|
|
1815
1825
|
// 实现时复用 craft.jobStart/craft.file 与 PhotoCraftDialog 的任务轮询模式。
|
|
1816
1826
|
|
|
1827
|
+
// ============================================================
|
|
1828
|
+
// 自动更新:npm/git 双信息源,一键 git pull(Host RPC)
|
|
1829
|
+
// ============================================================
|
|
1830
|
+
const PLUGIN_VERSION = '0.16.0';
|
|
1831
|
+
async function checkUpdate(silent) {
|
|
1832
|
+
update((s) => { s.updateInfo = null; s.updateChecking = true; s.updateMsg = null; });
|
|
1833
|
+
try {
|
|
1834
|
+
const info = await hostRpc('updateCheck');
|
|
1835
|
+
update((s) => { s.updateInfo = info; s.updateChecking = false; });
|
|
1836
|
+
return info;
|
|
1837
|
+
} catch (e) {
|
|
1838
|
+
update((s) => { s.updateInfo = null; s.updateChecking = false; s.updateMsg = silent ? null : String(e.message || e); });
|
|
1839
|
+
if (!silent) toast('更新检查失败:' + (e.message || e));
|
|
1840
|
+
return null;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
async function applyUpdate() {
|
|
1844
|
+
update((s) => { s.updateBusy = true; s.updateMsg = '正在拉取更新…'; });
|
|
1845
|
+
try {
|
|
1846
|
+
const res = await hostRpc('updateApply');
|
|
1847
|
+
if (!res.ok) {
|
|
1848
|
+
update((s) => { s.updateBusy = false; s.updateMsg = '更新失败:' + (res.output || '').slice(-300); });
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
if (res.hostChanged) {
|
|
1852
|
+
update((s) => { s.updateBusy = false; s.updateMsg = '已更新到 v' + res.version + ' — 后台已变更,请重启一次 Harness 后刷新页面'; });
|
|
1853
|
+
} else {
|
|
1854
|
+
update((s) => { s.updateBusy = false; s.updateMsg = '已更新到 v' + res.version + ' — 正在刷新页面…'; });
|
|
1855
|
+
setTimeout(() => location.reload(), 1200);
|
|
1856
|
+
}
|
|
1857
|
+
} catch (e) {
|
|
1858
|
+
update((s) => { s.updateBusy = false; s.updateMsg = '更新失败:' + (e.message || e); });
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1817
1862
|
// ============================================================
|
|
1818
1863
|
// 尺寸 / 比例(Toonflow 式:清晰度 × 比例 → 像素)
|
|
1819
1864
|
// ============================================================
|
|
@@ -2071,6 +2116,11 @@ window.__ModuleLoader__.load({
|
|
|
2071
2116
|
assetsTick: 0,
|
|
2072
2117
|
tick: 0,
|
|
2073
2118
|
craftStatus: null, // {at, data} | {at, error}
|
|
2119
|
+
updateInfo: null, // Host updateCheck 结果
|
|
2120
|
+
updateChecking: false,
|
|
2121
|
+
updateBusy: false,
|
|
2122
|
+
updateMsg: null,
|
|
2123
|
+
updateDismissed: false,
|
|
2074
2124
|
};
|
|
2075
2125
|
const listeners = new Set();
|
|
2076
2126
|
let saveTimer = null;
|
|
@@ -4146,6 +4196,12 @@ window.__ModuleLoader__.load({
|
|
|
4146
4196
|
: h('button', { type: 'button', className: 'dshic-btn', style: { fontWeight: 600, maxWidth: 180, overflow: 'hidden' }, onDoubleClick: () => setNaming(true), title: T('renameHint') },
|
|
4147
4197
|
h('span', { style: { overflow: 'hidden', textOverflow: 'ellipsis' } }, proj ? proj.name : '')),
|
|
4148
4198
|
h('div', { className: 'dshic-toolbar-sep' }),
|
|
4199
|
+
h('button', {
|
|
4200
|
+
type: 'button', className: cls('dshic-btn'), style: { fontSize: 11, color: 'var(--dsw-alias-label-secondary)', gap: 4 },
|
|
4201
|
+
title: T('updateBtn'), disabled: S.updateChecking,
|
|
4202
|
+
onClick: () => { checkUpdate(false).then((info) => { if (info && info.upToDate) toast(T('updateUpToDate')); }); },
|
|
4203
|
+
}, 'v' + PLUGIN_VERSION, S.updateChecking ? h('span', { className: 'dshic-spin' }) : (updAvail ? ' ⬆' : '')),
|
|
4204
|
+
h('div', { className: 'dshic-toolbar-sep' }),
|
|
4149
4205
|
h(Btn, {
|
|
4150
4206
|
active: S.rightOpen, title: T('toggleRight'),
|
|
4151
4207
|
onClick: () => update((s) => { s.rightOpen = !s.rightOpen; }),
|
|
@@ -4905,7 +4961,7 @@ window.__ModuleLoader__.load({
|
|
|
4905
4961
|
h(ApiListEditor, { key: 'imglist', kind: 'image' }),
|
|
4906
4962
|
h(ApiListEditor, { key: 'vidlist', kind: 'video' }),
|
|
4907
4963
|
h(ApiListEditor, { key: 'ttslist', kind: 'tts' }),
|
|
4908
|
-
h('div', { key: 'note', className: 'dshic-hint' }, 'DeepSeek Harness · dsh-infinite-canvas v0.
|
|
4964
|
+
h('div', { key: 'note', className: 'dshic-hint' }, 'DeepSeek Harness · dsh-infinite-canvas v0.17.0'),
|
|
4909
4965
|
];
|
|
4910
4966
|
}
|
|
4911
4967
|
|
|
@@ -5082,6 +5138,8 @@ window.__ModuleLoader__.load({
|
|
|
5082
5138
|
return () => el.removeEventListener('wheel', fn);
|
|
5083
5139
|
}, []);
|
|
5084
5140
|
const nodes = Object.values(S.graph.nodes);
|
|
5141
|
+
const upd = S.updateInfo;
|
|
5142
|
+
const updAvail = upd && !upd.upToDate;
|
|
5085
5143
|
return h('div', { className: 'dshic-page' },
|
|
5086
5144
|
!S.hostOk && S.hostError
|
|
5087
5145
|
? h('div', {
|
|
@@ -5095,6 +5153,44 @@ window.__ModuleLoader__.load({
|
|
|
5095
5153
|
h('span', { style: { opacity: 0.75, fontSize: 11 } }, '(' + S.hostError + ')'),
|
|
5096
5154
|
)
|
|
5097
5155
|
: null,
|
|
5156
|
+
updAvail && !S.updateDismissed
|
|
5157
|
+
? h('div', {
|
|
5158
|
+
style: {
|
|
5159
|
+
flex: 'none', display: 'flex', alignItems: 'center', gap: 8, padding: '6px 12px',
|
|
5160
|
+
background: 'color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent)',
|
|
5161
|
+
color: 'var(--dsw-alias-state-success-primary)', fontSize: 12, lineHeight: '18px',
|
|
5162
|
+
},
|
|
5163
|
+
},
|
|
5164
|
+
h('span', {}, '🚀 ' + T('updateAvailable') + ' v' + (upd.remoteVersion || '') + (upd.behind ? '(落后 ' + upd.behind + ' 个提交)' : '')),
|
|
5165
|
+
S.updateBusy
|
|
5166
|
+
? h('span', { className: 'dshic-spin' })
|
|
5167
|
+
: h('button', {
|
|
5168
|
+
type: 'button', className: 'dshic-mini',
|
|
5169
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
5170
|
+
onClick: applyUpdate,
|
|
5171
|
+
}, Ic('download', 11), T('updateNow')),
|
|
5172
|
+
h('a', {
|
|
5173
|
+
href: 'https://github.com/NINKCH/dsh-infinite_canvas/releases', target: '_blank', rel: 'noreferrer',
|
|
5174
|
+
style: { color: 'inherit', opacity: 0.8, fontSize: 11 },
|
|
5175
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
5176
|
+
}, T('releaseNotes') + ' ↗'),
|
|
5177
|
+
h('span', { style: { flex: 1 } }),
|
|
5178
|
+
h('button', {
|
|
5179
|
+
type: 'button', className: 'dshic-node-x', title: T('cancel'),
|
|
5180
|
+
onPointerDown: (e) => e.stopPropagation(),
|
|
5181
|
+
onClick: () => update((s) => { s.updateDismissed = true; }),
|
|
5182
|
+
}, '✕'),
|
|
5183
|
+
)
|
|
5184
|
+
: null,
|
|
5185
|
+
S.updateMsg
|
|
5186
|
+
? h('div', {
|
|
5187
|
+
style: {
|
|
5188
|
+
flex: 'none', padding: '6px 12px', fontSize: 12,
|
|
5189
|
+
background: 'color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent)',
|
|
5190
|
+
color: 'var(--dsw-alias-brand-primary)',
|
|
5191
|
+
},
|
|
5192
|
+
}, S.updateMsg)
|
|
5193
|
+
: null,
|
|
5098
5194
|
h(Toolbar),
|
|
5099
5195
|
h('div', { className: 'dshic-main' },
|
|
5100
5196
|
h('div', {
|
|
@@ -5188,6 +5284,8 @@ window.__ModuleLoader__.load({
|
|
|
5188
5284
|
const unsub = ctx.locale.subscribe(() => notify());
|
|
5189
5285
|
// ArtCraft 后期工具链状态探测(失败静默,Craft 功能各自降级)
|
|
5190
5286
|
probeCraft();
|
|
5287
|
+
// 检查更新(Host RPC;Host 旧版未带此方法时静默)
|
|
5288
|
+
checkUpdate(true);
|
|
5191
5289
|
// 后台路由探活:失败时在画布顶部给出可操作的提示
|
|
5192
5290
|
hostRpc('ping').then(() => { S.hostOk = true; S.hostError = null; notify(); }).catch((e) => {
|
|
5193
5291
|
S.hostOk = false;
|
package/index.js
CHANGED
|
@@ -6,7 +6,72 @@
|
|
|
6
6
|
* through the Harness `llm` Service — the same providers/models the user has
|
|
7
7
|
* configured for the Harness itself, no extra API key handling here.
|
|
8
8
|
*/
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
import fs from 'node:fs/promises';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
9
14
|
const NAME = 'infinite-canvas';
|
|
15
|
+
const VERSION = '0.17.0';
|
|
16
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
|
|
18
|
+
/** 运行 git 子命令(cwd = 插件目录 = 仓库根)。 */
|
|
19
|
+
function runGit(args, timeoutMs = 90000) {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
const started = Date.now();
|
|
22
|
+
const stdout = [];
|
|
23
|
+
const stderr = [];
|
|
24
|
+
let child;
|
|
25
|
+
try {
|
|
26
|
+
child = spawn('git', args.map(String), { cwd: HERE, windowsHide: true });
|
|
27
|
+
} catch (e) {
|
|
28
|
+
resolve({ ok: false, stdout: '', stderr: String(e?.message ?? e) });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const timer = setTimeout(() => { try { child.kill(); } catch { /* ignore */ } }, Math.max(5000, timeoutMs));
|
|
32
|
+
child.stdout?.setEncoding('utf8');
|
|
33
|
+
child.stderr?.setEncoding('utf8');
|
|
34
|
+
child.stdout?.on('data', (d) => stdout.push(String(d)));
|
|
35
|
+
child.stderr?.on('data', (d) => stderr.push(String(d)));
|
|
36
|
+
child.on('error', (e) => { clearTimeout(timer); resolve({ ok: false, stdout: stdout.join(''), stderr: String(e?.message ?? e) }); });
|
|
37
|
+
child.on('close', (code) => {
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
resolve({ ok: code === 0, code, stdout: stdout.join(''), stderr: stderr.join(''), ms: Date.now() - started });
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 更新检查:fetch origin + 对比本地/远端版本。 */
|
|
45
|
+
async function updateCheck() {
|
|
46
|
+
const fetchRes = await runGit(['fetch', 'origin', '--quiet'], 60000);
|
|
47
|
+
if (!fetchRes.ok) throw new Error('git fetch 失败:' + (fetchRes.stderr || '').slice(0, 300));
|
|
48
|
+
const head = (await runGit(['rev-parse', '--short', 'HEAD'])).stdout.trim();
|
|
49
|
+
const remoteHead = (await runGit(['rev-parse', '--short', 'origin/main'])).stdout.trim();
|
|
50
|
+
const behind = Number((await runGit(['rev-list', '--count', 'HEAD..origin/main'])).stdout.trim()) || 0;
|
|
51
|
+
let remoteVersion = null;
|
|
52
|
+
try {
|
|
53
|
+
const pj = await runGit(['show', 'origin/main:package.json']);
|
|
54
|
+
remoteVersion = JSON.parse(pj.stdout).version ?? null;
|
|
55
|
+
} catch { /* 远端无 package.json 时忽略 */ }
|
|
56
|
+
let runningVersion = null;
|
|
57
|
+
try { runningVersion = JSON.parse(await fs.readFile(path.join(HERE, 'package.json'), 'utf8')).version ?? null; } catch { /* 忽略 */ }
|
|
58
|
+
return { head, remoteHead, behind, remoteVersion, runningVersion, upToDate: head === remoteHead };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 应用更新:git pull --ff-only;报告 index.js 是否变更(决定是否需要重启 Harness)。 */
|
|
62
|
+
async function updateApply() {
|
|
63
|
+
const before = (await runGit(['rev-parse', 'HEAD:index.js'])).stdout.trim();
|
|
64
|
+
const pull = await runGit(['pull', '--ff-only', 'origin', 'main'], 180000);
|
|
65
|
+
const after = (await runGit(['rev-parse', 'HEAD:index.js'])).stdout.trim();
|
|
66
|
+
let version = null;
|
|
67
|
+
try { version = JSON.parse(await fs.readFile(path.join(HERE, 'package.json'), 'utf8')).version ?? null; } catch { /* 忽略 */ }
|
|
68
|
+
return {
|
|
69
|
+
ok: pull.ok,
|
|
70
|
+
output: ((pull.stdout || '') + '\n' + (pull.stderr || '')).trim().slice(-1500),
|
|
71
|
+
hostChanged: pull.ok && before !== after,
|
|
72
|
+
version,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
10
75
|
|
|
11
76
|
export const inject = ['webServer'];
|
|
12
77
|
|
|
@@ -86,9 +151,11 @@ export function apply(ctx) {
|
|
|
86
151
|
}
|
|
87
152
|
|
|
88
153
|
const methods = {
|
|
89
|
-
async ping() { return { ok: true, version:
|
|
154
|
+
async ping() { return { ok: true, version: VERSION }; },
|
|
90
155
|
async llmOptions() { return listLlmOptions(); },
|
|
91
156
|
async llmComplete(args) { return complete(args); },
|
|
157
|
+
async updateCheck() { return updateCheck(); },
|
|
158
|
+
async updateApply() { return updateApply(); },
|
|
92
159
|
async listSkills() {
|
|
93
160
|
const skills = ctx.get('skills');
|
|
94
161
|
if (!skills) return { skills: [], error: 'skills 服务不可用' };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ninkch/dsh-infinite-canvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Node-based short-drama studio for DeepSeek Harness: script → storyboard → assets → shots → voiced, subtitled final cut on an infinite canvas.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|