@ninkch/dsh-infinite-canvas 0.17.7 → 0.17.9

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.
Files changed (2) hide show
  1. package/client.js +94 -59
  2. package/package.json +1 -1
package/client.js CHANGED
@@ -356,6 +356,12 @@ window.__ModuleLoader__.load({
356
356
  'dataRootOpen': '打开目录',
357
357
  'dataRootMigrate': '迁移历史资产',
358
358
  'dataRootMigrated': '已迁移 {n} 个资产到数据根目录',
359
+ 'collapse': '折叠',
360
+ 'foldOpen': '展开',
361
+ 'craftReadyCount': '{r}/{n} 就绪',
362
+ 'craftDirLabel': '当前项目后期目录',
363
+ 'craftDataRootNeed': '请先在上方设置画布数据根目录:Craft 后期文件将存放于 数据根目录\\projects\\<项目>\\craft',
364
+ 'craftRootMismatch': 'ArtCraft 自身数据目录与画布数据根目录不一致:请在 ArtCraft 设置里把数据目录改成同一根目录,否则后期文件会落到两处',
359
365
  'dataRootImportBtn': '从数据目录导入',
360
366
  'dataRootImported': '导入完成:合并 {n} 个项目、补入 {a} 条资产元数据',
361
367
  'dataRootImportHint': '换电脑迁移:把整个数据目录拷到新机器 → 设置为数据根目录 → 点"从数据目录导入"即可恢复项目/节点/设定集/资产',
@@ -687,6 +693,12 @@ window.__ModuleLoader__.load({
687
693
  'dataRootOpen': 'Open folder',
688
694
  'dataRootMigrate': 'Migrate existing assets',
689
695
  'dataRootMigrated': 'Migrated {n} assets to the data root',
696
+ 'collapse': 'Collapse',
697
+ 'foldOpen': 'Expand',
698
+ 'craftReadyCount': '{r}/{n} ready',
699
+ 'craftDirLabel': 'Project craft dir',
700
+ 'craftDataRootNeed': 'Set the canvas data root above first: craft files go to <root>\\projects\\<project>\\craft',
701
+ 'craftRootMismatch': "ArtCraft's own data root differs from the canvas data root — point ArtCraft at the same root or craft files will land in two places",
690
702
  'dataRootImportBtn': 'Import from data root',
691
703
  'dataRootImported': 'Imported: {n} projects merged, {a} asset records added',
692
704
  'dataRootImportHint': 'Migration: copy the whole data folder to the new machine, set it as data root, then click "Import from data root" to restore projects/nodes/bible/assets',
@@ -1092,8 +1104,8 @@ window.__ModuleLoader__.load({
1092
1104
  try { localStorage.setItem(key, JSON.stringify(value)); } catch { /* 忽略配额错误 */ }
1093
1105
  }
1094
1106
  const DEFAULT_SETTINGS = {
1095
- // 生成参数一律"留空即不发送",由 Harness 适配器/模型用已配置的默认值(不在代码里写死)
1096
- textLlm: { provider: '', model: '', maxTokens: '', temperature: '' },
1107
+ // 文本模型全部用 Harness 的当前模型与默认参数:画布只选择模型,不单独配置生成参数
1108
+ textLlm: { provider: '', model: '' },
1097
1109
  imageApis: [], // [{id, name, baseUrl, apiKey, model, mode, size?, responseFormat?}]
1098
1110
  videoApis: [], // [{id, name, url, apiKey, model, protocol?, watermark?, generateAudio?}]
1099
1111
  ttsApis: [], // [{id, name, baseUrl, apiKey, model, voice}]
@@ -1107,7 +1119,7 @@ window.__ModuleLoader__.load({
1107
1119
  function loadSettings() {
1108
1120
  const s = lsGet(LS_SETTINGS, {}) || {};
1109
1121
  const out = {
1110
- textLlm: { ...DEFAULT_SETTINGS.textLlm, ...(s.textLlm || {}) },
1122
+ textLlm: { provider: (s.textLlm && s.textLlm.provider) || '', model: (s.textLlm && s.textLlm.model) || '' },
1111
1123
  imageApis: Array.isArray(s.imageApis) ? s.imageApis : [],
1112
1124
  videoApis: Array.isArray(s.videoApis) ? s.videoApis : [],
1113
1125
  ttsApis: Array.isArray(s.ttsApis) ? s.ttsApis : [],
@@ -1598,6 +1610,20 @@ window.__ModuleLoader__.load({
1598
1610
  }
1599
1611
  return { migrated: done, total: targets.length };
1600
1612
  }
1613
+ /** 数据根目录就绪后自动把浏览器 IndexedDB 里的存量资产搬到磁盘(替代原"迁移历史资产"按钮,全自动无需手动触发)。 */
1614
+ async function autoMigrateAssetsToRoot() {
1615
+ if (!S.dataRoot) return;
1616
+ try {
1617
+ const all = await idbAll();
1618
+ const pending = all.some((r) => r && r.blob && !r.rel);
1619
+ if (!pending) return;
1620
+ const r = await migrateAssetsToDisk();
1621
+ if (r.migrated) {
1622
+ toast(T('dataRootMigrated').replace('{n}', String(r.migrated)));
1623
+ update((s) => { s.assetsTick++; });
1624
+ }
1625
+ } catch { /* 静默:迁移失败不影响主流程,下次设置数据根目录时再试 */ }
1626
+ }
1601
1627
  function useAssetUrl(id) {
1602
1628
  const [url, setUrl] = useState(null);
1603
1629
  useEffect(() => {
@@ -2302,7 +2328,9 @@ window.__ModuleLoader__.load({
2302
2328
  const pid = S.activeId;
2303
2329
  const map = craftMapLoad(pid);
2304
2330
  if (!opts.force && map[rec.id]) return map[rec.id];
2305
- const rel = craftProjectSlug() + '/media/' + assetFileBase(rec) + extForMime(rec.mime, rec.name);
2331
+ // Craft 后期数据统一放在**画布数据根目录**的项目目录下:projects/<项目>/<slug 前缀去掉>/craft/…
2332
+ // (ArtCraft 数据根目录 = 画布数据根目录时,即落在 <root>/projects/<项目>/craft/media/)
2333
+ const rel = 'projects/' + craftProjectSlug() + '/craft/media/' + assetFileBase(rec) + extForMime(rec.mime, rec.name);
2306
2334
  const blob = rec.blob || await assetBlob(rec.id);
2307
2335
  if (!blob) throw new Error('资产内容不可用: ' + rec.id);
2308
2336
  await craft.write(rel, blob);
@@ -5331,12 +5359,12 @@ window.__ModuleLoader__.load({
5331
5359
  width: dims.w, height: dims.h,
5332
5360
  });
5333
5361
  try {
5334
- await craft.writeText(slug + '/craft/timeline.xml', xml);
5335
- await craft.writeText(slug + '/craft/subs.srt', buildSrt(items));
5362
+ await craft.writeText('projects/' + slug + '/craft/timeline.xml', xml);
5363
+ await craft.writeText('projects/' + slug + '/craft/subs.srt', buildSrt(items));
5336
5364
  } catch (e) { return; /* craftFetch 已提示 */ }
5337
5365
  setFcDialog({
5338
- args: JSON.stringify(['--project', slug + '/craft/drama.fcproj', '--import', slug + '/craft/timeline.xml', '--export', slug + '/out/final.mp4'], null, 1),
5339
- outRel: slug + '/out/final.mp4',
5366
+ args: JSON.stringify(['--project', 'projects/' + slug + '/craft/drama.fcproj', '--import', 'projects/' + slug + '/craft/timeline.xml', '--export', 'projects/' + slug + '/craft/out/final.mp4'], null, 1),
5367
+ outRel: 'projects/' + slug + '/craft/out/final.mp4',
5340
5368
  });
5341
5369
  };
5342
5370
  const submitFc = (args, outRel) => {
@@ -6131,13 +6159,12 @@ window.__ModuleLoader__.load({
6131
6159
  // 默认项 = 内置专家 persona + 内置创作方法论 canvas-craft(自动叠加)
6132
6160
  const options = [{ value: '', label: T('builtinExpert') }].concat((data.skills || []).map((x) => ({ value: x.name, label: x.name })));
6133
6161
  return h('div', { className: 'dshic-field' },
6134
- h('span', { className: 'dshic-field-label' }, label),
6162
+ h('span', { className: 'dshic-field-label' }, label, InfoTip(T('skillHint'))),
6135
6163
  h('select', {
6136
6164
  className: 'dshic-select', value: value || '',
6137
6165
  onChange: (e) => onChange(e.target.value),
6138
6166
  onPointerDown: (e) => e.stopPropagation(),
6139
- }, options.map((o) => h('option', { key: o.value, value: o.value }, o.label))),
6140
- h('div', { className: 'dshic-hint' }, T('skillHint')));
6167
+ }, options.map((o) => h('option', { key: o.value, value: o.value }, o.label))));
6141
6168
  }
6142
6169
 
6143
6170
  function LlmModelSelect() {
@@ -6188,7 +6215,6 @@ window.__ModuleLoader__.load({
6188
6215
  },
6189
6216
  onPointerDown: (e) => e.stopPropagation(),
6190
6217
  }),
6191
- h('div', { className: 'dshic-hint' }, T('llmHint')),
6192
6218
  );
6193
6219
  }
6194
6220
 
@@ -6202,6 +6228,13 @@ window.__ModuleLoader__.load({
6202
6228
  onPointerDown: (e) => e.stopPropagation(),
6203
6229
  }));
6204
6230
  }
6231
+ /** ⓘ 悬停说明:界面简洁化——长解释一律收进悬停提示(原生 title),不再平铺文字。 */
6232
+ function InfoTip(text) {
6233
+ return h('span', {
6234
+ title: String(text || ''), 'aria-label': String(text || ''),
6235
+ style: { flex: 'none', width: 14, height: 14, lineHeight: '13px', textAlign: 'center', fontSize: 10, borderRadius: '50%', border: '1px solid var(--dsw-alias-border-secondary, #777)', color: 'var(--dsw-alias-label-secondary, #999)', cursor: 'help', userSelect: 'none', opacity: 0.85 },
6236
+ }, 'i');
6237
+ }
6205
6238
  function ApiListEditor({ kind }) {
6206
6239
  const [s, setS] = useState(loadSettings);
6207
6240
  const [openId, setOpenId] = useState(null);
@@ -6351,10 +6384,19 @@ window.__ModuleLoader__.load({
6351
6384
  /** T2:设置面板 Craft 后期卡片(连接状态 / 工具灯 / 数据目录)。 */
6352
6385
  function CraftCard() {
6353
6386
  const [, force] = useState(0);
6387
+ const [open, setOpen] = useState(false); // 默认折叠:界面简洁,摘要常显
6354
6388
  useEffect(() => { probeCraft().then(() => force((x) => x + 1)); }, []);
6355
6389
  const cs = S.craftStatus;
6356
6390
  const data = cs && cs.data;
6357
6391
  const slug = craftProjectSlug();
6392
+ // Craft 后期目录 = **画布数据根目录** + 项目目录 + \craft(不再有 ArtCraft 独立数据目录的 second 口径)
6393
+ const craftDir = S.dataRoot ? (S.dataRoot.replace(/[\\/]+$/, '') + '\\projects\\' + slug + '\\craft') : '';
6394
+ // ArtCraft 自身的数据目录若与画布数据根目录不一致 → 后期文件会落到两处,必须提醒
6395
+ const artRoot = data && data.dataRoot ? data.dataRoot.path : '';
6396
+ const rootMismatch = !!(S.dataRoot && artRoot && artRoot.replace(/[\\/]+$/, '') !== S.dataRoot.replace(/[\\/]+$/, ''));
6397
+ // Craft 家族全量动态渲染:ArtCraft /status 返回几款 bins 就渲染几款(当前 12 款),不再写死
6398
+ const binKeys = (data && data.bins ? Object.keys(data.bins) : []).sort();
6399
+ const readyCount = binKeys.filter((k) => data.bins[k] && data.bins[k].found).length;
6358
6400
  const binRow = (key) => {
6359
6401
  const info = data && data.bins ? data.bins[key] : null;
6360
6402
  const ok = !!(info && info.found);
@@ -6366,18 +6408,22 @@ window.__ModuleLoader__.load({
6366
6408
  );
6367
6409
  };
6368
6410
  return h('div', { className: 'dshic-card' },
6369
- h('div', { className: 'dshic-row' },
6411
+ h('div', {
6412
+ className: 'dshic-row', style: { cursor: 'pointer' },
6413
+ onPointerDown: (e) => e.stopPropagation(), onClick: () => setOpen((x) => !x),
6414
+ title: open ? T('collapse') : T('foldOpen'),
6415
+ },
6370
6416
  Ic('film', 12),
6371
6417
  h('strong', { style: { fontSize: 12, flex: 1, minWidth: 0 } }, T('craftCard')),
6372
- h('button', { type: 'button', className: 'dshic-mini is-ghost', onPointerDown: (e) => e.stopPropagation(), onClick: () => { probeCraft().then(() => force((x) => x + 1)); } }, '↻'),
6418
+ data ? h('span', { className: 'dshic-hint', style: { flex: 'none' } }, T('craftReadyCount').replace('{r}', String(readyCount)).replace('{n}', String(binKeys.length))) : null,
6419
+ h('button', { type: 'button', className: 'dshic-mini is-ghost', onPointerDown: (e) => e.stopPropagation(), onClick: (e) => { e.stopPropagation(); probeCraft().then(() => force((x) => x + 1)); } }, '↻'),
6420
+ h('span', { style: { flex: 'none', fontSize: 10, transition: 'transform .15s', transform: open ? 'rotate(90deg)' : 'rotate(0deg)' } }, '▶'),
6373
6421
  ),
6374
- data
6375
- ? h('div', { className: 'dshic-hint' }, T('craftDataRoot') + ':' + ((data.dataRoot && data.dataRoot.path) || '—'))
6376
- : h('div', { className: 'dshic-err' }, T('craftUnreachable')),
6377
- data ? binRow('photocraft') : null,
6378
- data ? binRow('filmcraft') : null,
6379
- data ? binRow('effectcraft') : null,
6380
- data ? h('div', { className: 'dshic-hint' }, T('craftProjectDir') + ':' + slug + '/') : null,
6422
+ open ? [
6423
+ craftDir ? h('div', { className: 'dshic-hint' }, T('craftDirLabel') + ':' + craftDir) : h('div', { className: 'dshic-err' }, T('craftDataRootNeed')),
6424
+ rootMismatch ? h('div', { className: 'dshic-err' }, T('craftRootMismatch')) : null,
6425
+ ...(data ? binKeys.map((k) => binRow(k)) : []),
6426
+ ] : null,
6381
6427
  );
6382
6428
  }
6383
6429
 
@@ -6406,7 +6452,7 @@ window.__ModuleLoader__.load({
6406
6452
  setRunning(true); setErr(null); setProgress('…');
6407
6453
  try {
6408
6454
  const mediaRel = await ensureAssetOnDisk(rec);
6409
- const outRel = craftProjectSlug() + '/out/graded/' + rec.id + '-graded' + extForMime(rec.mime, rec.name);
6455
+ const outRel = 'projects/' + craftProjectSlug() + '/craft/out/graded/' + rec.id + '-graded' + extForMime(rec.mime, rec.name);
6410
6456
  const job = await craft.jobStart({
6411
6457
  bin: 'photocraft',
6412
6458
  args: ['run', mediaRel, '--cmd', cmd, '--params', JSON.stringify(paramsObj), '--out', outRel],
@@ -6514,7 +6560,6 @@ window.__ModuleLoader__.load({
6514
6560
  const [val, setVal] = useState('');
6515
6561
  const [busy, setBusy] = useState(false);
6516
6562
  const [picking, setPicking] = useState(false);
6517
- const [mig, setMig] = useState(null); // null | {done,total} | 'done'
6518
6563
  useEffect(() => { if (S.dataRoot) setVal(S.dataRoot); }, [S.dataRoot]);
6519
6564
  const save = async () => {
6520
6565
  if (busy) return;
@@ -6524,7 +6569,7 @@ window.__ModuleLoader__.load({
6524
6569
  await refreshDataRoot();
6525
6570
  toast(T('dataRootSaved') + (S.dataRoot ? ':' + S.dataRoot : ''));
6526
6571
  assetIndexSynced = false; // 换了目录:索引要往新目录重建
6527
- syncAssetIndexToHost();
6572
+ autoMigrateAssetsToRoot().then(() => syncAssetIndexToHost()); // 先搬存量资产,再对齐索引
6528
6573
  } catch (e) { toast('设置失败:' + ((e && e.message) || e)); }
6529
6574
  setBusy(false);
6530
6575
  };
@@ -6534,7 +6579,7 @@ window.__ModuleLoader__.load({
6534
6579
  setVal(S.dataRoot || root);
6535
6580
  toast(T('dataRootSaved') + ':' + S.dataRoot);
6536
6581
  assetIndexSynced = false; // 换了目录:索引要往新目录重建
6537
- syncAssetIndexToHost();
6582
+ autoMigrateAssetsToRoot().then(() => syncAssetIndexToHost());
6538
6583
  };
6539
6584
  const browse = async () => {
6540
6585
  if (busy || picking) return;
@@ -6576,18 +6621,12 @@ window.__ModuleLoader__.load({
6576
6621
  } catch (e) { toast('导入失败:' + ((e && e.message) || e)); }
6577
6622
  setBusy(false);
6578
6623
  };
6579
- const migrate = async () => {
6580
- if (busy || picking || mig) return;
6581
- setBusy(true); setMig({ done: 0, total: 0 });
6582
- try {
6583
- const r = await migrateAssetsToDisk((done, total) => setMig({ done, total }));
6584
- setMig('done');
6585
- toast(T('dataRootMigrated').replace('{n}', String(r.migrated)));
6586
- } catch (e) { setMig(null); toast('迁移失败:' + ((e && e.message) || e)); }
6587
- setBusy(false);
6588
- };
6589
6624
  return h('div', { key: 'dataroot', className: 'dshic-card' },
6590
- h('div', { className: 'dshic-row' }, Ic('image', 12), h('strong', { style: { fontSize: 12 } }, T('dataRootLabel'))),
6625
+ h('div', { className: 'dshic-row' },
6626
+ Ic('image', 12),
6627
+ h('strong', { style: { fontSize: 12, flex: 1, minWidth: 0 } }, T('dataRootLabel')),
6628
+ InfoTip(T('dataRootHint')),
6629
+ ),
6591
6630
  S.dataRootSupported === false
6592
6631
  ? h('div', { className: 'dshic-hint' }, T('dataRootUnsupported'))
6593
6632
  : [
@@ -6600,17 +6639,12 @@ window.__ModuleLoader__.load({
6600
6639
  h('button', { type: 'button', className: 'dshic-mini', disabled: busy || picking, onPointerDown: (e) => e.stopPropagation(), onClick: save }, T('dataRootSave')),
6601
6640
  ),
6602
6641
  picking ? h('div', { className: 'dshic-hint', key: 'picking' }, T('dataRootPicking')) : null,
6603
- h('div', { className: 'dshic-hint', key: 'hint' }, T('dataRootHint')),
6604
6642
  h('div', { className: 'dshic-row', key: 'row2' },
6605
6643
  S.dataRoot ? h('button', { type: 'button', className: 'dshic-mini', onPointerDown: (e) => e.stopPropagation(), onClick: openDir }, T('dataRootOpen')) : null,
6606
- S.dataRoot ? h('button', { type: 'button', className: 'dshic-mini', disabled: busy || picking || !!mig, onPointerDown: (e) => e.stopPropagation(), onClick: migrate }, mig === 'done' ? T('dataRootMigratedDone') : T('dataRootMigrate')) : null,
6607
- (mig && mig !== 'done') ? h('span', { style: { fontSize: 11, opacity: 0.85 } }, T('dataRootMigrating') + ' ' + mig.done + '/' + mig.total) : null,
6608
- ),
6609
- h('div', { className: 'dshic-row', key: 'row3' },
6610
6644
  S.dataRoot ? h('button', { type: 'button', className: 'dshic-mini is-ghost', disabled: busy || picking, onPointerDown: (e) => e.stopPropagation(), onClick: importFromRoot }, Ic('folder', 11), T('dataRootImportBtn')) : null,
6645
+ S.dataRoot ? InfoTip(T('dataRootImportHint')) : null,
6611
6646
  h('div', { className: 'dshic-flex' }),
6612
6647
  ),
6613
- S.dataRoot ? h('div', { className: 'dshic-hint', key: 'importHint' }, T('dataRootImportHint')) : null,
6614
6648
  ],
6615
6649
  );
6616
6650
  }
@@ -6626,8 +6660,6 @@ window.__ModuleLoader__.load({
6626
6660
  };
6627
6661
  const field = (label, value, onChange, placeholder, password) => h(Field, { label, value, onChange, placeholder, password });
6628
6662
  return [
6629
- h(CraftCard, { key: 'craft' }),
6630
- h(DataRootCard, { key: 'dataroot' }),
6631
6663
  h('div', { key: 'lang', className: 'dshic-card' },
6632
6664
  h('div', { className: 'dshic-row' }, Ic('text', 12), h('strong', { style: { fontSize: 12 } }, T('langLabel'))),
6633
6665
  h('select', {
@@ -6643,24 +6675,27 @@ window.__ModuleLoader__.load({
6643
6675
  h('option', { value: 'zh' }, T('langZh')),
6644
6676
  h('option', { value: 'en' }, T('langEn'))),
6645
6677
  ),
6646
- h('div', { key: 'llm', className: 'dshic-card' },
6647
- h('div', { className: 'dshic-row' }, Ic('spark', 12), h('strong', { style: { fontSize: 12 } }, T('llmLabel'))),
6648
- h(LlmModelSelect),
6678
+ h(DataRootCard, { key: 'dataroot' }),
6679
+ h(CraftCard, { key: 'craft' }),
6680
+ // 专家与技能卡:独立于模型选择
6681
+ h('div', { key: 'skills', className: 'dshic-card' },
6649
6682
  h('div', { className: 'dshic-row' },
6650
- h(Field, {
6651
- label: T('llmMaxTokens'), value: s.textLlm.maxTokens || '', placeholder: T('llmParamAuto'),
6652
- onChange: (v) => apply((d) => { d.textLlm.maxTokens = v; }),
6653
- }),
6654
- h(Field, {
6655
- label: T('llmTemperature'), value: s.textLlm.temperature || '', placeholder: T('llmParamAuto'),
6656
- onChange: (v) => apply((d) => { d.textLlm.temperature = v; }),
6657
- }),
6683
+ Ic('spark', 12),
6684
+ h('strong', { style: { fontSize: 12, flex: 1, minWidth: 0 } }, T('skillEnhance')),
6685
+ InfoTip(T('skillHint')),
6658
6686
  ),
6659
- h('div', { className: 'dshic-hint' }, T('llmParamHint')),
6660
- h('div', { className: 'dshic-hint' }, T('skillEnhance')),
6661
6687
  h(SkillSelect, { label: T('skillScript'), value: s.skillScript, onChange: (v) => apply((d) => { d.skillScript = v; }) }),
6662
6688
  h(SkillSelect, { label: T('skillStoryboard'), value: s.skillStoryboard, onChange: (v) => apply((d) => { d.skillStoryboard = v; }) }),
6663
6689
  ),
6690
+ // 文本模型卡:只选模型,参数全用 Harness 默认;紧挨图片/视频/TTS 模型卡片
6691
+ h('div', { key: 'llm', className: 'dshic-card' },
6692
+ h('div', { className: 'dshic-row' },
6693
+ Ic('spark', 12),
6694
+ h('strong', { style: { fontSize: 12, flex: 1, minWidth: 0 } }, T('llmLabel')),
6695
+ InfoTip(T('llmHint')),
6696
+ ),
6697
+ h(LlmModelSelect),
6698
+ ),
6664
6699
  h(ApiListEditor, { key: 'imglist', kind: 'image' }),
6665
6700
  h(ApiListEditor, { key: 'vidlist', kind: 'video' }),
6666
6701
  h(ApiListEditor, { key: 'ttslist', kind: 'tts' }),
@@ -7214,8 +7249,8 @@ window.__ModuleLoader__.load({
7214
7249
  probeCraft();
7215
7250
  // 检查更新(Host RPC;Host 旧版未带此方法时静默)
7216
7251
  checkUpdate(true);
7217
- // 数据根目录(Host 旧版静默降级为 IndexedDB 模式);就绪后把 IDB 资产元数据补齐进数据目录索引
7218
- refreshDataRoot().then(() => syncAssetIndexToHost()).catch(() => { /* 静默 */ });
7252
+ // 数据根目录(Host 旧版静默降级为 IndexedDB 模式);就绪后自动搬存量资产到磁盘,再对齐索引
7253
+ refreshDataRoot().then(() => autoMigrateAssetsToRoot()).then(() => syncAssetIndexToHost()).catch(() => { /* 静默 */ });
7219
7254
  // 对话 ↔ 画布同步(启动一次 + 3s 轮询;旧 Host 自动停轮询)
7220
7255
  hostSyncTick().then(() => pushAllProjectsToHost());
7221
7256
  const hostSyncTimer = setInterval(() => hostSyncTick(), 3000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ninkch/dsh-infinite-canvas",
3
- "version": "0.17.7",
3
+ "version": "0.17.9",
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",