@linxin666/dsh-remote-web-ui 0.3.9 → 0.3.11

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.
@@ -1 +1 @@
1
- {"version":3,"file":"UpdateEntry.d.ts","sourceRoot":"","sources":["../../../src/client/UpdateEntry.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAOnE,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;IACb,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;CACzB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,gBAAgB,+BA+GxD"}
1
+ {"version":3,"file":"UpdateEntry.d.ts","sourceRoot":"","sources":["../../../src/client/UpdateEntry.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAOnE,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;IACb,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;CACzB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,gBAAgB,+BAkHxD"}
@@ -108,5 +108,5 @@ export function UpdateEntry({ wide, t }) {
108
108
  };
109
109
  }, [probeAvailability]);
110
110
  const updateLabel = updateAvailable ? t("update.availableLabel") : t("update.label");
111
- return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.trigger, "data-wide": wide ? undefined : "rail", "data-update-available": updateAvailable ? "true" : undefined, "aria-label": updateLabel, "aria-expanded": open, title: updateLabel, onClick: openPanel, children: _jsx(IconDownloadOutline16, { size: wide ? 16 : 18 }) }), open && createPortal((_jsxs("div", { className: css.overlay, role: "presentation", children: [_jsx("div", { className: css.mask, "aria-hidden": "true", onClick: closePanel }), _jsx(UpdatePanel, { t: t, view: view, onClose: closePanel, onRecheck: () => { void check(); }, onStartUpdate: (status) => { void startUpdate(status); } })] })), document.body)] }));
111
+ return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: css.trigger, "data-wide": wide ? (updateAvailable ? "wide" : undefined) : "rail", "data-update-available": updateAvailable ? "true" : undefined, "aria-label": updateLabel, "aria-expanded": open, title: updateLabel, onClick: openPanel, children: [_jsx(IconDownloadOutline16, { size: wide ? 16 : 18 }), updateAvailable && wide && (_jsx("span", { className: css.updateBadgeText, children: t("update.badge") }))] }), open && createPortal((_jsxs("div", { className: css.overlay, role: "presentation", children: [_jsx("div", { className: css.mask, "aria-hidden": "true", onClick: closePanel }), _jsx(UpdatePanel, { t: t, view: view, onClose: closePanel, onRecheck: () => { void check(); }, onStartUpdate: (status) => { void startUpdate(status); } })] })), document.body)] }));
112
112
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAelG,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAqBrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACrE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAC/F,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAErE,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,0CAA0C;QAC1C,MAAM,EAAE,SAAS,CAAA;KAClB;IAED,UAAU,OAAO;QACf;;;;WAIG;QACH;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,4GAA4G;AAC5G,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAYD,wCAAwC;AACxC,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAoQ9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAA;AAelG,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAqBrD,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACrE,YAAY,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AACnE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,YAAY,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAC/F,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACzD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAErE,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,0CAA0C;QAC1C,MAAM,EAAE,SAAS,CAAA;KAClB;IAED,UAAU,OAAO;QACf;;;;WAIG;QACH;;;;;WAKG;QACH,oBAAoB,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,4BAA4B,CAAA;SAAE,CAAA;KAC3F;CACF;AAED,4GAA4G;AAC5G,MAAM,WAAW,uBAAuB;IACtC,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;CACd;AAED,mEAAmE;AACnE,MAAM,WAAW,4BAA4B;IAC3C,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,KAAK,CAAA;CACjB;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf;;;;WAIG;QACH,aAAa,CAAC,EAAE;YAAE,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;SAAE,CAAA;KAC1E;CACF;AAYD,wCAAwC;AACxC,eAAO,MAAM,MAAM,UAA+D,CAAA;AAElF;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAyQ9C"}
@@ -85,6 +85,12 @@ export function apply(ctx) {
85
85
  };
86
86
  }
87
87
  const t = ctx.locale.bind(NS);
88
+ // Hand the `remote` translate seat to the module-scope adaptation layer:
89
+ // the whale/compact-picker labels were rendered with the English fallback
90
+ // before any dictionary existed; the wiring plus the layer's own sync tick
91
+ // re-render them in the active locale.
92
+ if (adapt !== undefined)
93
+ adapt.translate = t;
88
94
  const binder = ctx.get('webUiSettings') ?? ctx.settingsScope;
89
95
  const settingsScope = binder.bind({ namespace: REMOTE_WEB_UI_NS });
90
96
  const enabled = () => {
@@ -2,6 +2,9 @@
2
2
  /** Simplified Chinese dictionary (the key-set source of truth). */
3
3
  export declare const zh: {
4
4
  'entry.label': string;
5
+ 'mobile.whale.open': string;
6
+ 'mobile.composer.pickModel': string;
7
+ 'mobile.composer.pickEffort': string;
5
8
  title: string;
6
9
  subtitle: string;
7
10
  'card.title': string;
@@ -109,6 +112,7 @@ export declare const zh: {
109
112
  'settings.saveFailed': string;
110
113
  'settings.invalidNumber': string;
111
114
  'update.label': string;
115
+ 'update.badge': string;
112
116
  'update.availableLabel': string;
113
117
  'update.title': string;
114
118
  'update.checking': string;
@@ -157,6 +161,9 @@ export type SettingsCardKey = RemoteKey;
157
161
  /** English dictionary, checked complete against the zh key set. */
158
162
  export declare const en: {
159
163
  'entry.label': string;
164
+ 'mobile.whale.open': string;
165
+ 'mobile.composer.pickModel': string;
166
+ 'mobile.composer.pickEffort': string;
160
167
  title: string;
161
168
  subtitle: string;
162
169
  'card.title': string;
@@ -264,6 +271,7 @@ export declare const en: {
264
271
  'settings.saveFailed': string;
265
272
  'settings.invalidNumber': string;
266
273
  'update.label': string;
274
+ 'update.badge': string;
267
275
  'update.availableLabel': string;
268
276
  'update.title': string;
269
277
  'update.checking': string;
@@ -1 +1 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAEvE,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuJmB,CAAA;AAElC,sCAAsC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAA;AAEvC,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAA;AAEvC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuJsB,CAAA"}
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,uEAAuE;AAEvE,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2JmB,CAAA;AAElC,sCAAsC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAA;AAEvC,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAA;AAEvC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2JsB,CAAA"}
@@ -2,6 +2,9 @@
2
2
  /** Simplified Chinese dictionary (the key-set source of truth). */
3
3
  export const zh = {
4
4
  'entry.label': '远程访问',
5
+ 'mobile.whale.open': '打开侧边栏',
6
+ 'mobile.composer.pickModel': '选择模型',
7
+ 'mobile.composer.pickEffort': '选择推理等级',
5
8
  'title': '远程访问',
6
9
  'subtitle': '通过手机或另一台电脑配对,远程使用同一份 Web 界面(官方界面 + 移动端适配)',
7
10
  'card.title': '设备配对',
@@ -111,6 +114,7 @@ export const zh = {
111
114
  'settings.invalidNumber': '请输入数字,留空则使用默认值。',
112
115
  // 更新面板(侧边栏更新按钮)。
113
116
  'update.label': '检查更新',
117
+ 'update.badge': '有更新',
114
118
  'update.availableLabel': '发现新版本,检查更新',
115
119
  'update.title': '检查更新',
116
120
  'update.checking': '正在检查远程仓库中的新版本…',
@@ -155,6 +159,9 @@ export const zh = {
155
159
  /** English dictionary, checked complete against the zh key set. */
156
160
  export const en = {
157
161
  'entry.label': 'Remote access',
162
+ 'mobile.whale.open': 'Open sidebar',
163
+ 'mobile.composer.pickModel': 'Pick model',
164
+ 'mobile.composer.pickEffort': 'Pick reasoning effort',
158
165
  'title': 'Remote access',
159
166
  'subtitle': 'Pair a phone or another computer to share the same Web GUI (official UI + mobile adaptation)',
160
167
  'card.title': 'Pair a device',
@@ -264,6 +271,7 @@ export const en = {
264
271
  'settings.invalidNumber': 'Enter a number, or leave blank to use the default.',
265
272
  // Update panel (the sidebar update trigger).
266
273
  'update.label': 'Check for updates',
274
+ 'update.badge': 'Update available',
267
275
  'update.availableLabel': 'New version available. Check for updates',
268
276
  'update.title': 'Check for updates',
269
277
  'update.checking': 'Checking the remote repository for newer releases…',
@@ -20,6 +20,7 @@
20
20
  * reference (MIT, v47-v78) keep their version comments.
21
21
  * @module @linxin666/dsh-remote-web-ui/client/mobile-adapt
22
22
  */
23
+ import type { RemoteKey } from './locales.ts';
23
24
  /** The window global the plugin apply() wires the layout service into. */
24
25
  export interface RemoteAdaptGlobal {
25
26
  evaluate: () => void;
@@ -27,6 +28,14 @@ export interface RemoteAdaptGlobal {
27
28
  toggleSidebar: (() => void) | null;
28
29
  /** Wired by the plugin apply once ctx.layout is live. */
29
30
  closeDetails: (() => void) | null;
31
+ /**
32
+ * Wired by the plugin apply once the `remote` locale namespace is bound:
33
+ * the translate seat the injected surfaces (whale, compact picker) read
34
+ * their labels from. Null until then; label reads fall back to English
35
+ * (the SDK's universal fallback) and the 600ms sync tick re-renders the
36
+ * labels once the seat is wired.
37
+ */
38
+ translate: ((key: RemoteKey) => string) | null;
30
39
  /** Plugin master switch: false reverts the layer, true re-evaluates. */
31
40
  setEnabled(on: boolean): void;
32
41
  /** Replays a pending closeDetails once the layout face is wired (the first apply ran before the wiring). */
@@ -1 +1 @@
1
- {"version":3,"file":"mobile-adapt.d.ts","sourceRoot":"","sources":["../../../src/client/mobile-adapt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,yDAAyD;IACzD,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAClC,yDAAyD;IACzD,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IACjC,wEAAwE;IACxE,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAA;IAC7B,4GAA4G;IAC5G,iBAAiB,IAAI,IAAI,CAAA;CAC1B;AAgOD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CA0oBvC"}
1
+ {"version":3,"file":"mobile-adapt.d.ts","sourceRoot":"","sources":["../../../src/client/mobile-adapt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,yDAAyD;IACzD,aAAa,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IAClC,yDAAyD;IACzD,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAA;IACjC;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,CAAC,GAAG,EAAE,SAAS,KAAK,MAAM,CAAC,GAAG,IAAI,CAAA;IAC9C,wEAAwE;IACxE,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAA;IAC7B,4GAA4G;IAC5G,iBAAiB,IAAI,IAAI,CAAA;CAC1B;AAgOD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAyqBvC"}
@@ -252,6 +252,20 @@ export function startMobileAdapt() {
252
252
  return;
253
253
  window.__dshRemoteAdaptInstalled = true;
254
254
  const w = window;
255
+ /**
256
+ * Label lookup for the injected surfaces: the `remote` namespace seat wired
257
+ * by the plugin apply, else the English fallback (the SDK's universal
258
+ * fallback; the sync tick re-renders the labels once the seat is live).
259
+ */
260
+ const surfaceText = (key, fallback) => {
261
+ try {
262
+ const out = w.__dshRemoteAdapt?.translate?.(key);
263
+ return typeof out === 'string' && out.length > 0 ? out : fallback;
264
+ }
265
+ catch {
266
+ return fallback;
267
+ }
268
+ };
255
269
  let active = false;
256
270
  let savedViewportContent = null;
257
271
  let whaleEl = null;
@@ -397,12 +411,26 @@ export function startMobileAdapt() {
397
411
  removeCompactPicker();
398
412
  return;
399
413
  }
400
- const zh = (navigator.language ?? '').toLowerCase().startsWith('zh');
401
414
  if (document.getElementById(MODEL_BTN_ID) === null) {
402
- tools.appendChild(makeCompactButton(MODEL_BTN_ID, zh ? '选择模型' : 'Pick model', CUBE_ICON, /模型|Model/));
415
+ // The cell pattern matches the OFFICIAL picker cell text (the official
416
+ // client's own zh/en copy), not plugin-owned text.
417
+ tools.appendChild(makeCompactButton(MODEL_BTN_ID, surfaceText('mobile.composer.pickModel', 'Pick model'), CUBE_ICON, /模型|Model/)); // i18n-allow: matches official picker cell text, not plugin copy
403
418
  }
404
419
  if (document.getElementById(EFFORT_BTN_ID) === null) {
405
- tools.appendChild(makeCompactButton(EFFORT_BTN_ID, zh ? '选择推理等级' : 'Pick reasoning effort', LEVELS_ICON, /推理等级|Reasoning|Effort/i));
420
+ tools.appendChild(makeCompactButton(EFFORT_BTN_ID, surfaceText('mobile.composer.pickEffort', 'Pick reasoning effort'), LEVELS_ICON, /推理等级|Reasoning|Effort/i)); // i18n-allow: matches official picker cell text, not plugin copy
421
+ }
422
+ // Titles re-read every tick so a locale switch (or the translate seat
423
+ // arriving after this layer installed) is picked up without a reload.
424
+ for (const [id, key, fallback] of [
425
+ [MODEL_BTN_ID, 'mobile.composer.pickModel', 'Pick model'],
426
+ [EFFORT_BTN_ID, 'mobile.composer.pickEffort', 'Pick reasoning effort'],
427
+ ]) {
428
+ const btn = document.getElementById(id);
429
+ if (btn === null)
430
+ continue;
431
+ const label = surfaceText(key, fallback);
432
+ btn.title = label;
433
+ btn.setAttribute('aria-label', label);
406
434
  }
407
435
  document.body.classList.add(COMPACT_CLASS);
408
436
  }
@@ -439,9 +467,9 @@ export function startMobileAdapt() {
439
467
  whale.id = WHALE_ID;
440
468
  whale.type = 'button';
441
469
  whale.dataset.dshPlugin = 'remote-web-ui';
442
- // Outside the slots i18n system (module-scope overlay), so the label
443
- // picks the browser language directly.
444
- const whaleLabel = (navigator.language ?? '').toLowerCase().startsWith('zh') ? '打开侧边栏' : 'Open sidebar';
470
+ // The label reads the `remote` namespace (wired by the plugin apply);
471
+ // before wiring it falls back to English and the sync tick re-renders it.
472
+ const whaleLabel = surfaceText('mobile.whale.open', 'Open sidebar');
445
473
  whale.title = whaleLabel;
446
474
  whale.setAttribute('aria-label', whaleLabel);
447
475
  whale.innerHTML = `<svg viewBox="0 0 23.16 17.04" fill="none" aria-hidden="true"><path d="${FISH_PATH}" fill="currentColor"/></svg>`;
@@ -558,6 +586,12 @@ export function startMobileAdapt() {
558
586
  applyWhalePos();
559
587
  whaleEl.style.display = show ? '' : 'none';
560
588
  whaleShown = show;
589
+ // Re-read the label every tick so a locale switch is picked up live.
590
+ const whaleLabel = surfaceText('mobile.whale.open', 'Open sidebar');
591
+ if (whaleEl.title !== whaleLabel)
592
+ whaleEl.title = whaleLabel;
593
+ if (whaleEl.getAttribute('aria-label') !== whaleLabel)
594
+ whaleEl.setAttribute('aria-label', whaleLabel);
561
595
  document.body.classList.toggle(RAIL_HIDDEN_CLASS, collapsed);
562
596
  disableRowDrag();
563
597
  seatHeaderActions();
@@ -949,6 +983,7 @@ export function startMobileAdapt() {
949
983
  evaluate,
950
984
  toggleSidebar: null,
951
985
  closeDetails: null,
986
+ translate: null,
952
987
  setEnabled(on) {
953
988
  adaptEnabled = on;
954
989
  if (on)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-remote-web-ui",
3
3
  "description": "Scan-to-pair remote access for the dsh web GUI that shares one official interface: a QR beside the settings button pairs phones and PCs into the same Web GUI (a portrait-touch adaptation layer for phones, full desktop on PCs) through one-time tokens and revocable device sessions, with a LAN bind toggle, optional Cloudflare tunnel, and one-click family self-update",
4
- "version": "0.3.9",
4
+ "version": "0.3.11",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -36,7 +36,7 @@
36
36
  "license": "Apache-2.0",
37
37
  "dsh": {
38
38
  "engines": {
39
- "dsh": ">=0.1.2-alpha.1"
39
+ "dsh": ">=0.1.2-alpha.3"
40
40
  },
41
41
  "bundle": {
42
42
  "patch": "./cordis.patch.yml"
@@ -63,22 +63,22 @@
63
63
  },
64
64
  "devDependencies": {
65
65
  "@deepseek-ai/cordis": "^4.0.2",
66
- "@deepseek-ai/dsh-agent": "^0.1.2-alpha.2",
67
- "@deepseek-ai/dsh-api-remotes": "^0.1.2-alpha.2",
68
- "@deepseek-ai/dsh-api-session-controller": "^0.1.2-alpha.2",
69
- "@deepseek-ai/dsh-api-workspace-controller": "^0.1.2-alpha.2",
70
- "@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.2",
71
- "@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.2",
72
- "@deepseek-ai/dsh-client-store": "^0.1.2-alpha.2",
73
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-alpha.2",
74
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-alpha.2",
75
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.2",
76
- "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.2-alpha.2",
77
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.2",
78
- "@deepseek-ai/dsh-commands": "^0.1.2-alpha.2",
79
- "@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.2",
80
- "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.2",
81
- "@deepseek-ai/dsh-settings": "^0.1.2-alpha.2",
66
+ "@deepseek-ai/dsh-agent": "^0.1.2-alpha.3",
67
+ "@deepseek-ai/dsh-api-remotes": "^0.1.2-alpha.3",
68
+ "@deepseek-ai/dsh-api-session-controller": "^0.1.2-alpha.3",
69
+ "@deepseek-ai/dsh-api-workspace-controller": "^0.1.2-alpha.3",
70
+ "@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.3",
71
+ "@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.3",
72
+ "@deepseek-ai/dsh-client-store": "^0.1.2-alpha.3",
73
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-alpha.3",
74
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.2-alpha.3",
75
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.3",
76
+ "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.2-alpha.3",
77
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.3",
78
+ "@deepseek-ai/dsh-commands": "^0.1.2-alpha.3",
79
+ "@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.3",
80
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.3",
81
+ "@deepseek-ai/dsh-settings": "^0.1.2-alpha.3",
82
82
  "@testing-library/dom": "^10.4.1",
83
83
  "@testing-library/react": "^16.3.2",
84
84
  "@types/node": "^22.20.0",
@@ -121,7 +121,7 @@ export function UpdateEntry({ wide, t }: UpdateEntryProps) {
121
121
  <button
122
122
  type="button"
123
123
  className={css.trigger}
124
- data-wide={wide ? undefined : "rail"}
124
+ data-wide={wide ? (updateAvailable ? "wide" : undefined) : "rail"}
125
125
  data-update-available={updateAvailable ? "true" : undefined}
126
126
  aria-label={updateLabel}
127
127
  aria-expanded={open}
@@ -129,6 +129,9 @@ export function UpdateEntry({ wide, t }: UpdateEntryProps) {
129
129
  onClick={openPanel}
130
130
  >
131
131
  <IconDownloadOutline16 size={wide ? 16 : 18} />
132
+ {updateAvailable && wide && (
133
+ <span className={css.updateBadgeText}>{t("update.badge")}</span>
134
+ )}
132
135
  </button>
133
136
  {open && createPortal((
134
137
  <div className={css.overlay} role="presentation">
@@ -166,6 +166,11 @@ export function apply(ctx: ClientContext): void {
166
166
  }
167
167
 
168
168
  const t = ctx.locale.bind(NS)
169
+ // Hand the `remote` translate seat to the module-scope adaptation layer:
170
+ // the whale/compact-picker labels were rendered with the English fallback
171
+ // before any dictionary existed; the wiring plus the layer's own sync tick
172
+ // re-render them in the active locale.
173
+ if (adapt !== undefined) adapt.translate = t
169
174
  const binder = ctx.get('webUiSettings') ?? ctx.settingsScope
170
175
  const settingsScope = binder.bind<RemoteSettings>({ namespace: REMOTE_WEB_UI_NS })
171
176
  const enabled = (): boolean => {
@@ -3,6 +3,9 @@
3
3
  /** Simplified Chinese dictionary (the key-set source of truth). */
4
4
  export const zh = {
5
5
  'entry.label': '远程访问',
6
+ 'mobile.whale.open': '打开侧边栏',
7
+ 'mobile.composer.pickModel': '选择模型',
8
+ 'mobile.composer.pickEffort': '选择推理等级',
6
9
  'title': '远程访问',
7
10
  'subtitle': '通过手机或另一台电脑配对,远程使用同一份 Web 界面(官方界面 + 移动端适配)',
8
11
  'card.title': '设备配对',
@@ -112,6 +115,7 @@ export const zh = {
112
115
  'settings.invalidNumber': '请输入数字,留空则使用默认值。',
113
116
  // 更新面板(侧边栏更新按钮)。
114
117
  'update.label': '检查更新',
118
+ 'update.badge': '有更新',
115
119
  'update.availableLabel': '发现新版本,检查更新',
116
120
  'update.title': '检查更新',
117
121
  'update.checking': '正在检查远程仓库中的新版本…',
@@ -163,6 +167,9 @@ export type SettingsCardKey = RemoteKey
163
167
  /** English dictionary, checked complete against the zh key set. */
164
168
  export const en = {
165
169
  'entry.label': 'Remote access',
170
+ 'mobile.whale.open': 'Open sidebar',
171
+ 'mobile.composer.pickModel': 'Pick model',
172
+ 'mobile.composer.pickEffort': 'Pick reasoning effort',
166
173
  'title': 'Remote access',
167
174
  'subtitle': 'Pair a phone or another computer to share the same Web GUI (official UI + mobile adaptation)',
168
175
  'card.title': 'Pair a device',
@@ -272,6 +279,7 @@ export const en = {
272
279
  'settings.invalidNumber': 'Enter a number, or leave blank to use the default.',
273
280
  // Update panel (the sidebar update trigger).
274
281
  'update.label': 'Check for updates',
282
+ 'update.badge': 'Update available',
275
283
  'update.availableLabel': 'New version available. Check for updates',
276
284
  'update.title': 'Check for updates',
277
285
  'update.checking': 'Checking the remote repository for newer releases…',
@@ -21,6 +21,8 @@
21
21
  * @module @linxin666/dsh-remote-web-ui/client/mobile-adapt
22
22
  */
23
23
 
24
+ import type { RemoteKey } from './locales.ts'
25
+
24
26
  /** The window global the plugin apply() wires the layout service into. */
25
27
  export interface RemoteAdaptGlobal {
26
28
  evaluate: () => void
@@ -28,6 +30,14 @@ export interface RemoteAdaptGlobal {
28
30
  toggleSidebar: (() => void) | null
29
31
  /** Wired by the plugin apply once ctx.layout is live. */
30
32
  closeDetails: (() => void) | null
33
+ /**
34
+ * Wired by the plugin apply once the `remote` locale namespace is bound:
35
+ * the translate seat the injected surfaces (whale, compact picker) read
36
+ * their labels from. Null until then; label reads fall back to English
37
+ * (the SDK's universal fallback) and the 600ms sync tick re-renders the
38
+ * labels once the seat is wired.
39
+ */
40
+ translate: ((key: RemoteKey) => string) | null
31
41
  /** Plugin master switch: false reverts the layer, true re-evaluates. */
32
42
  setEnabled(on: boolean): void
33
43
  /** Replays a pending closeDetails once the layout face is wired (the first apply ran before the wiring). */
@@ -266,6 +276,19 @@ export function startMobileAdapt(): void {
266
276
  ;(window as unknown as { __dshRemoteAdaptInstalled?: boolean }).__dshRemoteAdaptInstalled = true
267
277
 
268
278
  const w = window as unknown as { __dshRemoteAdapt?: RemoteAdaptGlobal } & Record<string, unknown>
279
+ /**
280
+ * Label lookup for the injected surfaces: the `remote` namespace seat wired
281
+ * by the plugin apply, else the English fallback (the SDK's universal
282
+ * fallback; the sync tick re-renders the labels once the seat is live).
283
+ */
284
+ const surfaceText = (key: RemoteKey, fallback: string): string => {
285
+ try {
286
+ const out = w.__dshRemoteAdapt?.translate?.(key)
287
+ return typeof out === 'string' && out.length > 0 ? out : fallback
288
+ } catch {
289
+ return fallback
290
+ }
291
+ }
269
292
  let active = false
270
293
  let savedViewportContent: string | null = null
271
294
  let whaleEl: HTMLButtonElement | null = null
@@ -409,12 +432,25 @@ export function startMobileAdapt(): void {
409
432
  removeCompactPicker()
410
433
  return
411
434
  }
412
- const zh = (navigator.language ?? '').toLowerCase().startsWith('zh')
413
435
  if (document.getElementById(MODEL_BTN_ID) === null) {
414
- tools.appendChild(makeCompactButton(MODEL_BTN_ID, zh ? '选择模型' : 'Pick model', CUBE_ICON, /模型|Model/))
436
+ // The cell pattern matches the OFFICIAL picker cell text (the official
437
+ // client's own zh/en copy), not plugin-owned text.
438
+ tools.appendChild(makeCompactButton(MODEL_BTN_ID, surfaceText('mobile.composer.pickModel', 'Pick model'), CUBE_ICON, /模型|Model/)) // i18n-allow: matches official picker cell text, not plugin copy
415
439
  }
416
440
  if (document.getElementById(EFFORT_BTN_ID) === null) {
417
- tools.appendChild(makeCompactButton(EFFORT_BTN_ID, zh ? '选择推理等级' : 'Pick reasoning effort', LEVELS_ICON, /推理等级|Reasoning|Effort/i))
441
+ tools.appendChild(makeCompactButton(EFFORT_BTN_ID, surfaceText('mobile.composer.pickEffort', 'Pick reasoning effort'), LEVELS_ICON, /推理等级|Reasoning|Effort/i)) // i18n-allow: matches official picker cell text, not plugin copy
442
+ }
443
+ // Titles re-read every tick so a locale switch (or the translate seat
444
+ // arriving after this layer installed) is picked up without a reload.
445
+ for (const [id, key, fallback] of [
446
+ [MODEL_BTN_ID, 'mobile.composer.pickModel', 'Pick model'],
447
+ [EFFORT_BTN_ID, 'mobile.composer.pickEffort', 'Pick reasoning effort'],
448
+ ] as const) {
449
+ const btn = document.getElementById(id)
450
+ if (btn === null) continue
451
+ const label = surfaceText(key, fallback)
452
+ btn.title = label
453
+ btn.setAttribute('aria-label', label)
418
454
  }
419
455
  document.body.classList.add(COMPACT_CLASS)
420
456
  }
@@ -446,9 +482,9 @@ export function startMobileAdapt(): void {
446
482
  whale.id = WHALE_ID
447
483
  whale.type = 'button'
448
484
  whale.dataset.dshPlugin = 'remote-web-ui'
449
- // Outside the slots i18n system (module-scope overlay), so the label
450
- // picks the browser language directly.
451
- const whaleLabel = (navigator.language ?? '').toLowerCase().startsWith('zh') ? '打开侧边栏' : 'Open sidebar'
485
+ // The label reads the `remote` namespace (wired by the plugin apply);
486
+ // before wiring it falls back to English and the sync tick re-renders it.
487
+ const whaleLabel = surfaceText('mobile.whale.open', 'Open sidebar')
452
488
  whale.title = whaleLabel
453
489
  whale.setAttribute('aria-label', whaleLabel)
454
490
  whale.innerHTML = `<svg viewBox="0 0 23.16 17.04" fill="none" aria-hidden="true"><path d="${FISH_PATH}" fill="currentColor"/></svg>`
@@ -553,6 +589,10 @@ export function startMobileAdapt(): void {
553
589
  if (show && !whaleShown) applyWhalePos()
554
590
  whaleEl.style.display = show ? '' : 'none'
555
591
  whaleShown = show
592
+ // Re-read the label every tick so a locale switch is picked up live.
593
+ const whaleLabel = surfaceText('mobile.whale.open', 'Open sidebar')
594
+ if (whaleEl.title !== whaleLabel) whaleEl.title = whaleLabel
595
+ if (whaleEl.getAttribute('aria-label') !== whaleLabel) whaleEl.setAttribute('aria-label', whaleLabel)
556
596
  document.body.classList.toggle(RAIL_HIDDEN_CLASS, collapsed)
557
597
  disableRowDrag()
558
598
  seatHeaderActions()
@@ -898,6 +938,7 @@ export function startMobileAdapt(): void {
898
938
  evaluate,
899
939
  toggleSidebar: null,
900
940
  closeDetails: null,
941
+ translate: null,
901
942
  setEnabled(on: boolean): void {
902
943
  adaptEnabled = on
903
944
  if (on) evaluate()
@@ -40,6 +40,10 @@
40
40
  transition: background-color 120ms ease, color 120ms ease, box-shadow 120ms ease;
41
41
  }
42
42
 
43
+ .trigger[data-update-available] {
44
+ color: var(--dsw-alias-brand-primary);
45
+ }
46
+
43
47
  .trigger[data-update-available]::after {
44
48
  position: absolute;
45
49
  top: 4px;
@@ -66,6 +70,22 @@
66
70
  padding: 0 10px;
67
71
  }
68
72
 
73
+ .trigger[data-update-available][data-wide='wide'] {
74
+ flex: none;
75
+ gap: 6px;
76
+ }
77
+
78
+ .trigger[data-update-available][data-wide='wide']::after {
79
+ display: none;
80
+ }
81
+
82
+ .updateBadgeText {
83
+ font-size: 13px;
84
+ font-weight: 500;
85
+ line-height: 1;
86
+ white-space: nowrap;
87
+ }
88
+
69
89
  .trigger:hover:not(:disabled) {
70
90
  background: var(--dsw-alias-interactive-bg-hover);
71
91
  color: var(--dsw-alias-label-primary);