dsh-jiuling-remote-mobile 1.0.17 → 1.0.18

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/lib/client.js CHANGED
@@ -415,6 +415,38 @@ window.__ModuleLoader__.load({
415
415
  };
416
416
  }, []);
417
417
 
418
+ /**
419
+ * 让官方前端的模型目录**失效重载**。
420
+ *
421
+ * ## ⚠️ 这一步为什么不能省
422
+ *
423
+ * 官方 `ModelCatalogDirectory` 在页面加载时就 `load()` 一次并进入
424
+ * `status === 'ready'`,此后**点菜单一个请求都不发**
425
+ * (`dsh-client-ui-model-selection/lib/client.js:100`:ready 时直接
426
+ * 返回旧值)。所以「配对之后模型列表变成电脑的」这件事,只把宿主那边的
427
+ * 缓存换掉是**看不见的** —— 前端那一层永远捧着配对前拉到的手机列表。
428
+ *
429
+ * 这正是用户报的「预设列表正确地加载了电脑预设,但模型列表还是没同步」:
430
+ * 预设走 `remoteExportList`,**没有缓存层**、每次重读,所以它是对的。
431
+ *
432
+ * `modelDirectories` 是官方那个服务实例(`super(ctx, "modelDirectories")`),
433
+ * `.catalog.refresh()` 就是官方在 `llm/adapters-updated` 时走的同一条路 ——
434
+ * 走公开方法,不碰前端 bundle 内部类。
435
+ *
436
+ * `ctx.get` 拿不到服务时静默跳过(见 `injected()` 里 `modelDirectories`
437
+ * 的注释):最坏是回到「要点一下刷新按钮」的旧行为,不会报错。
438
+ */
439
+ var reloadModelCatalog = function () {
440
+ try {
441
+ var models = props.modelDirectories;
442
+ if (models && models.catalog && typeof models.catalog.refresh === 'function') {
443
+ models.catalog.refresh();
444
+ }
445
+ } catch (error) {
446
+ /* 服务结构变了就只更新宿主缓存,不打扰用户 */
447
+ }
448
+ };
449
+
418
450
  // 每次打开都回到「手机」页签,避免上次停在电脑目录造成困惑。
419
451
  react.useEffect(
420
452
  function () {
@@ -539,6 +571,10 @@ window.__ModuleLoader__.load({
539
571
  // 必须作废,否则用户会看到上一台电脑的目录。
540
572
  clearDesktopDirCache();
541
573
  patch({ path: null, listing: null });
574
+ // ⚠️ 前端那份模型目录是**永久缓存**的(见 `reloadModelCatalog`),
575
+ // 不作废的话用户接下来看到的仍然是配对前那份手机模型列表 ——
576
+ // 就是用户报的「模型列表还是没同步」。
577
+ reloadModelCatalog();
542
578
  },
543
579
  function (reason) {
544
580
  if (!aliveRef.current) return;
package/lib/index.js CHANGED
@@ -678,7 +678,7 @@ export function apply(ctx) {
678
678
  const watchers = new Set();
679
679
 
680
680
  /**
681
- * 电脑侧的静默缓存:`{ catalog, presets, at }`。
681
+ * 电脑侧的静默缓存:`{ catalog, presets, directory, at }`。
682
682
  *
683
683
  * ## 为什么要缓存(用户需求原文)
684
684
  *
@@ -686,10 +686,17 @@ export function apply(ctx) {
686
686
  * 以及目录树这样体验会好很多……因为一般项目目录是不会变的,可以提前加载」。
687
687
  *
688
688
  * 配对后立刻拉一次,之后前端一打开模型菜单就有内容,不用等一轮往返。
689
- * 目录树**不进这个缓存** —— 它按路径变化,缓存了反而会显示过期内容,
690
- * 由前端每次进目录时实时拉。
689
+ *
690
+ * ⚠️ **`directory` 只缓存「根」那一层**(`listDirectory` 不给 path 时返回的
691
+ * home),因为「点开电脑页签」是每次都逃不掉的第一步,而它现在必然要过一轮
692
+ * 隧道往返 —— 用户看到的就是那个转圈(原话:「还是有一个加载过程,
693
+ * 不能预加载吗」)。
694
+ *
695
+ * **整棵目录树不进缓存**:电脑上几万个目录,全拉一遍既慢又没意义,而且
696
+ * 会大面积显示过期内容。点进子目录仍然实时拉(前端另有 `desktopDirCache`
697
+ * 兜住「同一层来回点」)。根目录的过期窗口由「刷新」按钮关闭。
691
698
  */
692
- const desktopCache = { catalog: null, presets: null, at: 0 };
699
+ const desktopCache = { catalog: null, presets: null, directory: null, at: 0 };
693
700
 
694
701
  /**
695
702
  * 静默预拉电脑的模型目录与预设列表。
@@ -703,7 +710,7 @@ export function apply(ctx) {
703
710
  */
704
711
  const prefetchDesktop = async (why) => {
705
712
  if (config.token === EMPTY_TOKEN) return;
706
- const [catalog, presets] = await Promise.all([
713
+ const [catalog, presets, directory] = await Promise.all([
707
714
  callRemote(config, 'modelCatalog', {}).catch((error) => {
708
715
  ctx.logger?.warn?.(`[dsh-remote-mobile] 预拉模型列表失败(${why}): ${String(error?.message ?? error)}`);
709
716
  return null;
@@ -712,16 +719,49 @@ export function apply(ctx) {
712
719
  ctx.logger?.warn?.(`[dsh-remote-mobile] 预拉预设列表失败(${why}): ${String(error?.message ?? error)}`);
713
720
  return null;
714
721
  }),
722
+ // 根目录那一层:不带 path ⇒ 电脑返回它的 home,正是用户点开
723
+ // 「电脑」页签第一眼看到的东西。见 `desktopCache.directory` 的注释。
724
+ callRemote(config, 'listDirectory', {}).catch((error) => {
725
+ ctx.logger?.warn?.(`[dsh-remote-mobile] 预拉目录树失败(${why}): ${String(error?.message ?? error)}`);
726
+ return null;
727
+ }),
715
728
  ]);
716
729
  if (catalog !== null) desktopCache.catalog = catalog;
717
730
  if (presets !== null) desktopCache.presets = presets;
731
+ if (directory !== null) desktopCache.directory = directory;
718
732
  desktopCache.at = Date.now();
719
733
  const models = catalog?.groups?.reduce((n, g) => n + (g.models?.length ?? 0), 0) ?? 0;
734
+ const dirs = directory?.entries?.length ?? directory?.items?.length ?? 0;
720
735
  ctx.logger?.info?.(
721
- `[dsh-remote-mobile] 已预拉电脑数据(${why}):${models} 个模型、${presets?.presets?.length ?? 0} 个预设`,
736
+ `[dsh-remote-mobile] 已预拉电脑数据(${why}):${models} 个模型、${presets?.presets?.length ?? 0} 个预设、根目录 ${dirs} 项`,
722
737
  );
723
738
  };
724
739
 
740
+ /**
741
+ * 取电脑的模型目录,缓存为空就**当场拉一次**。
742
+ *
743
+ * ⚠️ **不能只依赖配对时的预拉**。那是异步的后台动作,而前端页面一加载
744
+ * 就会调一次 `modelCatalog`(`ModelCatalogDirectory` 构造后立刻 `load()`,
745
+ * 见 `dsh-client-ui-model-selection/lib/client.js:354`)—— 谁先到谁决定
746
+ * 用户看到什么。拿不到电脑那份就返回手机本地列表,症状正是用户报的
747
+ * 「预设列表正确地加载了电脑预设,但模型列表还是没同步」:预设走
748
+ * `remoteExportList`,**没有缓存层**、每次重读,所以它是对的。
749
+ *
750
+ * @returns 电脑的模型目录;拉不到时 `null`。
751
+ */
752
+ const desktopCatalog = async () => {
753
+ if (desktopCache.catalog !== null) return desktopCache.catalog;
754
+ try {
755
+ desktopCache.catalog = await callRemote(config, 'modelCatalog', {});
756
+ return desktopCache.catalog;
757
+ } catch (error) {
758
+ ctx.logger?.warn?.(
759
+ `[dsh-remote-mobile] 取电脑模型列表失败: ${String(error?.message ?? error)}`,
760
+ );
761
+ return null;
762
+ }
763
+ };
764
+
725
765
  /**
726
766
  * 把电脑的模型目录并进手机本地的目录。
727
767
  *
@@ -1152,8 +1192,10 @@ export function apply(ctx) {
1152
1192
  const originalCatalog = controller.modelCatalog.bind(controller);
1153
1193
  controller.modelCatalog = async (...args) => {
1154
1194
  const local = await originalCatalog(...args);
1155
- if (!paired() || desktopCache.catalog === null) return local;
1156
- return mergeCatalog(local, desktopCache.catalog);
1195
+ if (!paired()) return local;
1196
+ const desktop = await desktopCatalog();
1197
+ if (desktop === null) return local;
1198
+ return mergeCatalog(local, desktop);
1157
1199
  };
1158
1200
  undo.push(() => {
1159
1201
  controller.modelCatalog = originalCatalog;
@@ -1569,9 +1611,24 @@ async function handleBridge(ctx, config, io) {
1569
1611
  case 'ping':
1570
1612
  return callRemote(config, 'ping', {});
1571
1613
 
1572
- /** 列电脑上的目录。 */
1573
- case 'listDesktop':
1574
- return callRemote(config, 'listDirectory', { path: params.path });
1614
+ /**
1615
+ * 列电脑上的目录。
1616
+ *
1617
+ * ⚠️ **根那层命中预拉缓存就直接回,不打隧道**:用户点开「电脑」页签时
1618
+ * 看的正是这一层,而它每一轮往返都要穿过 Cloudflare 隧道 ——
1619
+ * 那就是用户抱怨的那个「还是有一个加载过程」(原话:「目录树都提前存档
1620
+ * 手机本地了,为什么还是还加载呢,不能预加载吗」)。
1621
+ *
1622
+ * 只在**没给 path**(= 要 home)且预拉成功过时才走缓存。子目录照旧实时拉,
1623
+ * 前端 `desktopDirCache` 负责「同一层来回点」,两边分工见各自的注释。
1624
+ */
1625
+ case 'listDesktop': {
1626
+ const wantRoot = params.path === undefined || params.path === null || params.path === '';
1627
+ if (wantRoot && io.desktopCache?.directory != null) return io.desktopCache.directory;
1628
+ const listing = await callRemote(config, 'listDirectory', { path: params.path });
1629
+ if (wantRoot && io.desktopCache !== undefined) io.desktopCache.directory = listing;
1630
+ return listing;
1631
+ }
1575
1632
 
1576
1633
  /**
1577
1634
  * 电脑上的模型目录。
@@ -1615,9 +1672,23 @@ async function handleBridge(ctx, config, io) {
1615
1672
  // ⚠️ 刷新到的模型/预设要**写回缓存**,否则「刷新按钮」只更新了它自己
1616
1673
  // 那份响应,前端模型菜单读的是宿主拦截器里的缓存 —— 用户会看到
1617
1674
  // 「点了刷新,模型列表没变」。
1675
+ //
1676
+ // 根目录那一层同理:刷完不回写的话,用户接下来点开「电脑」页签
1677
+ // 命中的还是**刷新前**那份预拉缓存,看起来就是「刷新没生效」。
1678
+ //
1679
+ // ⚠️ **判据是「拉回来的这一层就是缓存的那一层」**(同 path),
1680
+ // 不是「有没有带 path」:前端在根层点刷新时传的是当前显示的绝对路径
1681
+ // (`loc.path`,也就是 home),照样是带 path 的一次调用 —— 只看
1682
+ // `params.path` 空不空会把根层刷新漏掉。
1618
1683
  if (io.desktopCache !== undefined) {
1619
1684
  if (result.models?.ok === true) io.desktopCache.catalog = result.models.value;
1620
1685
  if (result.presets?.ok === true) io.desktopCache.presets = result.presets.value;
1686
+ if (
1687
+ result.directory?.ok === true &&
1688
+ io.desktopCache.directory?.path === result.directory.value?.path
1689
+ ) {
1690
+ io.desktopCache.directory = result.directory.value;
1691
+ }
1621
1692
  io.desktopCache.at = Date.now();
1622
1693
  }
1623
1694
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-jiuling-remote-mobile",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "description": "DSH 远程目录(手机端):在选文件夹流程里多出「电脑」页签,进入电脑目录、在电脑上发起对话,并把电脑的实时消息流同步到手机本地界面(只传文本,不映射桌面)。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",