@linxin666/dsh-remote-web-ui 0.1.12 → 0.1.14
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.i18n.yaml +6 -0
- package/README.md +17 -3
- package/README.zh.md +171 -0
- package/lib/client.js +171 -100
- package/lib/client.js.map +1 -1
- package/lib/index.js +338 -31
- package/lib/mobile.js +109 -33
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +3 -2
- package/lib/types/client/RemoteSettingsCard.d.ts +4 -0
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +3 -1
- package/lib/types/client/UpdateEntry.d.ts.map +1 -1
- package/lib/types/client/UpdateEntry.js +10 -2
- package/lib/types/client/deep-link.js +10 -5
- package/lib/types/client/locales.d.ts +8 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +10 -2
- package/lib/types/client/update-api.d.ts +10 -0
- package/lib/types/client/update-api.d.ts.map +1 -1
- package/lib/types/client/update-api.js +22 -2
- package/lib/types/index.d.ts +7 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +7 -1
- package/lib/types/mobile/api.d.ts +9 -2
- package/lib/types/mobile/api.d.ts.map +1 -1
- package/lib/types/mobile/api.js +7 -3
- package/lib/types/mobile/mobile-styles.d.ts +1 -1
- package/lib/types/mobile/mobile-styles.d.ts.map +1 -1
- package/lib/types/mobile/mobile-styles.js +3 -0
- package/lib/types/mobile/views/App.d.ts +3 -1
- package/lib/types/mobile/views/App.d.ts.map +1 -1
- package/lib/types/mobile/views/App.js +18 -3
- package/lib/types/mobile/views/ChatView.d.ts +6 -0
- package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
- package/lib/types/mobile/views/ChatView.js +95 -7
- package/lib/types/mobile/views/ChatView.test.js +161 -3
- package/lib/types/mobile/views/WorkspaceView.d.ts +3 -1
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +1 -1
- package/lib/types/mobile/views/WorkspaceView.js +9 -2
- package/lib/types/mobile/views/WorkspaceView.test.d.ts +2 -0
- package/lib/types/mobile/views/WorkspaceView.test.d.ts.map +1 -0
- package/lib/types/mobile/views/WorkspaceView.test.js +56 -0
- package/lib/types/mobile-api.d.ts +2 -0
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +17 -2
- package/lib/types/update.d.ts +5 -1
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +122 -26
- package/package.json +1 -11
- package/src/client/PluginSettingsCard.tsx +30 -5
- package/src/client/RemoteSettingsCard.tsx +18 -0
- package/src/client/UpdateEntry.tsx +10 -2
- package/src/client/deep-link.ts +10 -4
- package/src/client/locales.ts +10 -2
- package/src/client/settings-card.module.css +139 -144
- package/src/client/update-api.ts +22 -2
- package/src/index.ts +14 -1
- package/src/mobile/api.ts +14 -2
- package/src/mobile/mobile-styles.ts +3 -0
- package/src/mobile/views/App.tsx +22 -3
- package/src/mobile/views/ChatView.test.tsx +185 -3
- package/src/mobile/views/ChatView.tsx +102 -8
- package/src/mobile/views/WorkspaceView.test.tsx +66 -0
- package/src/mobile/views/WorkspaceView.tsx +11 -2
- package/src/mobile-api.ts +20 -2
- package/src/update.ts +116 -26
package/lib/types/update.d.ts
CHANGED
|
@@ -180,9 +180,13 @@ export interface UpdateRunDeps {
|
|
|
180
180
|
spawnImpl?: typeof spawn;
|
|
181
181
|
/** Hard timeout; the child is killed on expiry. */
|
|
182
182
|
timeoutMs?: number;
|
|
183
|
+
/** Platform override (defaults to process.platform; test seam). */
|
|
184
|
+
platform?: NodeJS.Platform;
|
|
183
185
|
}
|
|
184
186
|
/**
|
|
185
|
-
* Run the update
|
|
187
|
+
* Run the update inside the profile directory. Tries pnpm first, falls back
|
|
188
|
+
* to corepack and then npx when the previous command is missing (ENOENT);
|
|
189
|
+
* all candidates share one hard timeout and keep accumulating output.
|
|
186
190
|
* @param deps - profile dir, package list, and spawn/timeout seams.
|
|
187
191
|
* @returns the outcome with captured output.
|
|
188
192
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/update.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAE1C,iDAAiD;AACjD,eAAO,MAAM,YAAY,+BAA+B,CAAA;AAExD,oEAAoE;AACpE,eAAO,MAAM,YAAY,gBAAgB,CAAA;AAEzC,sEAAsE;AACtE,eAAO,MAAM,iBAAiB,8BAA8B,CAAA;AAE5D,+EAA+E;AAC/E,eAAO,MAAM,YAAY,iCAAiC,CAAA;AAQ1D,0EAA0E;AAC1E,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,2DAA2D;IAC3D,UAAU,EAAE,MAAM,EAAE,CAAA;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CASlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CA2B5D;AAYD,yDAAyD;AACzD,MAAM,WAAW,YAAY;IAC3B,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,kBAAkB,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAahF;AAED,6DAA6D;AAC7D,KAAK,cAAc,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAA;AAE9D,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAG1D;AAED,kDAAkD;AAClD,MAAM,WAAW,mBAAmB;IAClC,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oDAAoD;IACpD,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,kEAAkE;AAClE,MAAM,WAAW,YAAY;IAC3B,iGAAiG;IACjG,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAAA;IAChC,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qDAAqD;IACrD,QAAQ,EAAE,mBAAmB,EAAE,CAAA;IAC/B,qDAAqD;IACrD,QAAQ,EAAE,OAAO,CAAA;IACjB,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,4EAA4E;IAC5E,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IAC9C,oEAAoE;IACpE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CACvD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GACrC,MAAM,GAAG,SAAS,CASpB;AAED,kFAAkF;AAClF,MAAM,WAAW,YAAY;IAC3B,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAA;IAClB,qDAAqD;IACrD,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE;IAAE,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,GACpC,YAAY,GAAG;IAAE,KAAK,EAAE,WAAW,GAAG,MAAM,CAAA;CAAE,CAiBhD;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAQhF;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,EAC9E,SAAS,SAAS,GACjB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAiB7B;AAaD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC,CAgD/E;AAED,4DAA4D;AAC5D,MAAM,MAAM,eAAe;AACzB,2BAA2B;AACzB,cAAc;AAChB,6CAA6C;GAC3C,SAAS;AACX,2CAA2C;GACzC,WAAW;AACb,iEAAiE;GAC/D,MAAM;AACR,4BAA4B;GAC1B,aAAa,CAAA;AAEjB,gCAAgC;AAChC,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAA;IACX,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAA;IACd,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gEAAgE;IAChE,SAAS,CAAC,EAAE,eAAe,CAAA;CAC5B;AAED,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAA;IAClB,sCAAsC;IACtC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3B,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/update.ts"],"names":[],"mappings":"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"}
|
package/lib/types/update.js
CHANGED
|
@@ -286,48 +286,144 @@ export async function checkUpdates(deps) {
|
|
|
286
286
|
/** Cap on captured pnpm output (keeps error payloads bounded). */
|
|
287
287
|
const OUTPUT_CAP = 16 * 1024;
|
|
288
288
|
/**
|
|
289
|
-
*
|
|
289
|
+
* Windows cmd command-not-found stderr. With shell:true a missing shim
|
|
290
|
+
* exits with code 1 (cmd cannot report ENOENT), so the fallback chain
|
|
291
|
+
* detects this message instead of the spawn error event.
|
|
292
|
+
*/
|
|
293
|
+
const WIN_CMD_MISSING_RE = /not recognized as an internal or external command/i;
|
|
294
|
+
/**
|
|
295
|
+
* Run the update inside the profile directory. Tries pnpm first, falls back
|
|
296
|
+
* to corepack and then npx when the previous command is missing (ENOENT);
|
|
297
|
+
* all candidates share one hard timeout and keep accumulating output.
|
|
290
298
|
* @param deps - profile dir, package list, and spawn/timeout seams.
|
|
291
299
|
* @returns the outcome with captured output.
|
|
292
300
|
*/
|
|
293
301
|
export function runUpdate(deps) {
|
|
294
302
|
return new Promise((resolve) => {
|
|
295
|
-
const
|
|
303
|
+
const spawnImpl = deps.spawnImpl ?? spawn;
|
|
304
|
+
const packages = deps.packages;
|
|
305
|
+
const platform = deps.platform ?? process.platform;
|
|
306
|
+
// Windows ships pnpm/corepack/npx as .cmd shims; Node's spawn cannot
|
|
307
|
+
// start .cmd files directly (ENOENT even when installed), so route
|
|
308
|
+
// them through cmd.exe there. POSIX spawns stay shell-free.
|
|
309
|
+
const spawnOptions = {
|
|
296
310
|
cwd: deps.profileDir,
|
|
297
311
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
298
|
-
|
|
312
|
+
...(platform === 'win32' ? { shell: true } : {}),
|
|
313
|
+
};
|
|
314
|
+
// Ordered fallback chain: each is tried only when the previous one is
|
|
315
|
+
// missing on PATH (ENOENT on the spawn error event, never on close).
|
|
316
|
+
const candidates = [
|
|
317
|
+
{ command: 'pnpm', args: ['update', ...packages] },
|
|
318
|
+
{ command: 'corepack', args: ['pnpm', 'update', ...packages] },
|
|
319
|
+
{ command: 'npx', args: ['--yes', 'pnpm', 'update', ...packages] },
|
|
320
|
+
];
|
|
321
|
+
// `output` accumulates across candidates for UI display; `currentOutput`
|
|
322
|
+
// is reset per candidate and carries only that candidate's own diagnostics
|
|
323
|
+
// (its tail is subject to OUTPUT_CAP). The win32 missing-command test must
|
|
324
|
+
// run against `currentOutput` so a previous candidate's 'not recognized'
|
|
325
|
+
// stderr cannot misclassify a real failure in the next one.
|
|
299
326
|
let output = '';
|
|
327
|
+
let currentOutput = '';
|
|
300
328
|
const append = (chunk) => {
|
|
301
329
|
output += chunk.toString('utf8');
|
|
302
330
|
if (output.length > OUTPUT_CAP)
|
|
303
331
|
output = output.slice(output.length - OUTPUT_CAP);
|
|
332
|
+
currentOutput += chunk.toString('utf8');
|
|
333
|
+
if (currentOutput.length > OUTPUT_CAP)
|
|
334
|
+
currentOutput = currentOutput.slice(currentOutput.length - OUTPUT_CAP);
|
|
335
|
+
};
|
|
336
|
+
let currentChild;
|
|
337
|
+
// Terminal guard: once a result is produced the promise is settled and no
|
|
338
|
+
// further candidate is started (a killed child's late close must not
|
|
339
|
+
// respawn anything after a timeout resolution).
|
|
340
|
+
let finished = false;
|
|
341
|
+
const finish = (result) => {
|
|
342
|
+
if (finished)
|
|
343
|
+
return;
|
|
344
|
+
finished = true;
|
|
345
|
+
clearTimeout(timer);
|
|
346
|
+
resolve(result);
|
|
304
347
|
};
|
|
305
|
-
child.stdout?.on('data', append);
|
|
306
|
-
child.stderr?.on('data', append);
|
|
307
348
|
const timer = setTimeout(() => {
|
|
308
|
-
|
|
309
|
-
|
|
349
|
+
if (finished)
|
|
350
|
+
return;
|
|
351
|
+
if (platform === 'win32') {
|
|
352
|
+
// cmd.exe under shell:true only kills the shell wrapper; kill the whole
|
|
353
|
+
// process tree best-effort so pnpm/npx do not keep running.
|
|
354
|
+
const pid = currentChild?.pid;
|
|
355
|
+
if (pid !== undefined && pid > 0) {
|
|
356
|
+
try {
|
|
357
|
+
spawnImpl('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore' });
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
// Best-effort kill; fall through to the timeout result.
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
currentChild?.kill('SIGTERM');
|
|
366
|
+
}
|
|
367
|
+
finish({ ok: false, exitCode: null, output, error: 'update timed out; install process killed', errorCode: 'timeout' });
|
|
310
368
|
}, deps.timeoutMs ?? 10 * 60_000);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
369
|
+
const runCandidate = (index) => {
|
|
370
|
+
if (finished)
|
|
371
|
+
return;
|
|
372
|
+
if (index >= candidates.length) {
|
|
373
|
+
finish({
|
|
374
|
+
ok: false,
|
|
375
|
+
exitCode: null,
|
|
376
|
+
output,
|
|
377
|
+
error: 'pnpm not found on PATH (tried pnpm, corepack, npx); install pnpm and restart the app',
|
|
378
|
+
errorCode: 'pnpm-missing',
|
|
379
|
+
});
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const candidate = candidates[index];
|
|
383
|
+
currentOutput = '';
|
|
384
|
+
const child = spawnImpl(candidate.command, candidate.args, spawnOptions);
|
|
385
|
+
currentChild = child;
|
|
386
|
+
let settled = false;
|
|
387
|
+
const once = (fn) => {
|
|
388
|
+
if (settled)
|
|
389
|
+
return;
|
|
390
|
+
settled = true;
|
|
391
|
+
fn();
|
|
392
|
+
};
|
|
393
|
+
child.stdout?.on('data', append);
|
|
394
|
+
child.stderr?.on('data', append);
|
|
395
|
+
child.on('error', (error) => {
|
|
396
|
+
once(() => {
|
|
397
|
+
if (error.code === 'ENOENT') {
|
|
398
|
+
// Command missing: try the next candidate in the chain.
|
|
399
|
+
runCandidate(index + 1);
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
finish({ ok: false, exitCode: null, output, error: error.message, errorCode: undefined });
|
|
403
|
+
}
|
|
404
|
+
});
|
|
320
405
|
});
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
406
|
+
child.on('close', (code) => {
|
|
407
|
+
if (settled || finished)
|
|
408
|
+
return;
|
|
409
|
+
// Windows + shell: a missing command reports exit 1 with
|
|
410
|
+
// 'not recognized' instead of ENOENT — keep the chain going. Checked
|
|
411
|
+
// against this candidate's own output so a prior fallback's stderr
|
|
412
|
+
// cannot misclassify a real failure here.
|
|
413
|
+
if (platform === 'win32' && code !== 0 && WIN_CMD_MISSING_RE.test(currentOutput)) {
|
|
414
|
+
runCandidate(index + 1);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
settled = true;
|
|
418
|
+
finish({
|
|
419
|
+
ok: code === 0,
|
|
420
|
+
exitCode: code,
|
|
421
|
+
output,
|
|
422
|
+
error: code === 0 ? undefined : 'pnpm exited with code ' + String(code),
|
|
423
|
+
...(code === 0 ? {} : { errorCode: 'pnpm-failed' }),
|
|
424
|
+
});
|
|
330
425
|
});
|
|
331
|
-
}
|
|
426
|
+
};
|
|
427
|
+
runCandidate(0);
|
|
332
428
|
});
|
|
333
429
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-remote-web-ui",
|
|
3
3
|
"description": "Mobile remote control for the dsh web GUI: scan-to-pair QR entry beside the settings button, one-time pairing tokens, live device status, and revocable mobile sessions",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.14",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -55,16 +55,6 @@
|
|
|
55
55
|
"zod": "^4.4.3"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
|
|
59
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
|
|
60
|
-
"@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
|
|
61
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
|
|
62
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-sidebar": "^0.1.0-rc.6",
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
|
|
65
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
|
|
66
|
-
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
|
|
67
|
-
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
|
|
68
58
|
"react": "^18.2.0",
|
|
69
59
|
"react-dom": "^18.2.0"
|
|
70
60
|
},
|
|
@@ -41,13 +41,14 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
41
41
|
if (!state.available) return null
|
|
42
42
|
const title = props.t(props.titleKey)
|
|
43
43
|
const blocked = !state.dirty || state.invalid || state.saving
|
|
44
|
+
const cardClass = open ? `${css.cardOpen} ${css.card}` : css.card
|
|
44
45
|
// The namespace exists but the Host does not serve it to this client (the
|
|
45
46
|
// official settings allowlist omits third-party namespaces): show a card
|
|
46
47
|
// that explains the gap instead of vanishing, so a missing card never
|
|
47
48
|
// reads as a missing plugin.
|
|
48
49
|
if (!state.exposed) {
|
|
49
50
|
return (
|
|
50
|
-
<li className={
|
|
51
|
+
<li className={cardClass}>
|
|
51
52
|
<button
|
|
52
53
|
type="button"
|
|
53
54
|
className={css.header}
|
|
@@ -59,7 +60,19 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
59
60
|
<span className={css.name}>{title}</span>
|
|
60
61
|
<span className={css.description}>{props.t(props.descriptionKey)}</span>
|
|
61
62
|
</span>
|
|
62
|
-
<
|
|
63
|
+
<svg
|
|
64
|
+
width="14"
|
|
65
|
+
height="14"
|
|
66
|
+
viewBox="0 0 14 14"
|
|
67
|
+
fill="none"
|
|
68
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
69
|
+
className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron}
|
|
70
|
+
>
|
|
71
|
+
<path
|
|
72
|
+
d="M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z"
|
|
73
|
+
fill="currentColor"
|
|
74
|
+
/>
|
|
75
|
+
</svg>
|
|
63
76
|
</button>
|
|
64
77
|
{open
|
|
65
78
|
? (
|
|
@@ -72,7 +85,7 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
72
85
|
)
|
|
73
86
|
}
|
|
74
87
|
return (
|
|
75
|
-
<li className={
|
|
88
|
+
<li className={cardClass}>
|
|
76
89
|
<button
|
|
77
90
|
type="button"
|
|
78
91
|
className={css.header}
|
|
@@ -85,7 +98,19 @@ export function PluginSettingsCard(props: PluginSettingsCardProps) {
|
|
|
85
98
|
<span className={css.description}>{props.t(props.descriptionKey)}</span>
|
|
86
99
|
</span>
|
|
87
100
|
{state.dirty ? <span className={css.pending}>{props.t('settings.unsaved')}</span> : null}
|
|
88
|
-
<
|
|
101
|
+
<svg
|
|
102
|
+
width="14"
|
|
103
|
+
height="14"
|
|
104
|
+
viewBox="0 0 14 14"
|
|
105
|
+
fill="none"
|
|
106
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
107
|
+
className={open ? `${css.chevron} ${css.chevronOpen}` : css.chevron}
|
|
108
|
+
>
|
|
109
|
+
<path
|
|
110
|
+
d="M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z"
|
|
111
|
+
fill="currentColor"
|
|
112
|
+
/>
|
|
113
|
+
</svg>
|
|
89
114
|
</button>
|
|
90
115
|
{open
|
|
91
116
|
? (
|
|
@@ -234,4 +259,4 @@ export function BooleanField(props: FieldProps & {
|
|
|
234
259
|
<p className={css.hint}>{props.hint}</p>
|
|
235
260
|
</div>
|
|
236
261
|
)
|
|
237
|
-
}
|
|
262
|
+
}
|
|
@@ -27,6 +27,8 @@ export interface RemoteSettings {
|
|
|
27
27
|
publicBaseUrl?: string
|
|
28
28
|
/** When on, the plugin runs its own Cloudflare quick tunnel automatically. */
|
|
29
29
|
autoTunnel?: boolean
|
|
30
|
+
/** Mobile composer: plain Enter sends; off means Enter inserts a newline. */
|
|
31
|
+
mobileEnterToSend?: boolean
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/** What the remote-control card renders. */
|
|
@@ -47,6 +49,8 @@ export interface RemoteSettingsCardState extends CardShell {
|
|
|
47
49
|
publicBaseUrl: CardFieldState
|
|
48
50
|
/** Auto public tunnel switch. */
|
|
49
51
|
autoTunnel: CardFieldState
|
|
52
|
+
/** Mobile composer Enter-to-send switch. */
|
|
53
|
+
mobileEnterToSend: CardFieldState
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
/** The registration-side face the card's slot entry injects. */
|
|
@@ -73,6 +77,7 @@ export class RemoteSettingsCardController {
|
|
|
73
77
|
booleanField('requirePairingForLan'),
|
|
74
78
|
textField('publicBaseUrl'),
|
|
75
79
|
booleanField('autoTunnel'),
|
|
80
|
+
booleanField('mobileEnterToSend'),
|
|
76
81
|
])
|
|
77
82
|
this.store = this.form.bind(() => this.projection())
|
|
78
83
|
}
|
|
@@ -88,6 +93,7 @@ export class RemoteSettingsCardController {
|
|
|
88
93
|
requirePairingForLan: this.form.field('requirePairingForLan'),
|
|
89
94
|
publicBaseUrl: this.form.field('publicBaseUrl'),
|
|
90
95
|
autoTunnel: this.form.field('autoTunnel'),
|
|
96
|
+
mobileEnterToSend: this.form.field('mobileEnterToSend'),
|
|
91
97
|
}
|
|
92
98
|
}
|
|
93
99
|
|
|
@@ -215,6 +221,18 @@ export function RemoteSettingsCard(props: RemoteSettingsCardProps) {
|
|
|
215
221
|
onEdit={(text) => { props.edit('autoTunnel', text) }}
|
|
216
222
|
onReset={() => { props.resetField('autoTunnel') }}
|
|
217
223
|
/>
|
|
224
|
+
<BooleanField
|
|
225
|
+
id="settings-remote-mobile-enter"
|
|
226
|
+
label={t('settings.mobileEnterToSend')}
|
|
227
|
+
hint={t('settings.mobileEnterToSendHint')}
|
|
228
|
+
inheritLabel={t('settings.inherit')}
|
|
229
|
+
onLabel={t('settings.on')}
|
|
230
|
+
offLabel={t('settings.off')}
|
|
231
|
+
{...fieldProps}
|
|
232
|
+
{...state.mobileEnterToSend}
|
|
233
|
+
onEdit={(text) => { props.edit('mobileEnterToSend', text) }}
|
|
234
|
+
onReset={() => { props.resetField('mobileEnterToSend') }}
|
|
235
|
+
/>
|
|
218
236
|
</PluginSettingsCard>
|
|
219
237
|
)
|
|
220
238
|
}
|
|
@@ -10,7 +10,7 @@ import { createPortal } from "react-dom"
|
|
|
10
10
|
import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'
|
|
11
11
|
import { IconDownloadOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
12
12
|
import type { UpdateStatus } from '../update.ts'
|
|
13
|
-
import { fetchUpdateStatus, runUpdate } from "./update-api.ts"
|
|
13
|
+
import { fetchUpdateStatus, runUpdate, UpdateStatusError } from "./update-api.ts"
|
|
14
14
|
import { UpdatePanel, type UpdateView } from "./UpdatePanel.tsx"
|
|
15
15
|
import css from "./remote.module.css"
|
|
16
16
|
|
|
@@ -36,7 +36,15 @@ export function UpdateEntry({ wide, t }: UpdateEntryProps) {
|
|
|
36
36
|
let status: UpdateStatus
|
|
37
37
|
try {
|
|
38
38
|
status = await fetchUpdateStatus()
|
|
39
|
-
} catch {
|
|
39
|
+
} catch (error) {
|
|
40
|
+
// HTTP 404: the update route is not mounted — the host process runs an
|
|
41
|
+
// older plugin build (client refreshed, host did not). Restarting dsh
|
|
42
|
+
// web loads the new plugin; a plain network failure gets the generic
|
|
43
|
+
// offline copy instead of a misleading "cannot reach update source".
|
|
44
|
+
if (error instanceof UpdateStatusError && error.status === 404) {
|
|
45
|
+
setView({ kind: "error", message: t("update.unmounted"), detail: t("update.unmountedDetail") })
|
|
46
|
+
return
|
|
47
|
+
}
|
|
40
48
|
setView({ kind: "error", message: t("update.offline"), detail: t("update.offlineDetail") })
|
|
41
49
|
return
|
|
42
50
|
}
|
package/src/client/deep-link.ts
CHANGED
|
@@ -74,7 +74,7 @@ export function runPairBootFlow(ctx: Context, search: string, page: PageSurface
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
/** Accept the token, then
|
|
77
|
+
/** Accept the token, then enter the matching desktop/mobile surface. */
|
|
78
78
|
async function runAccept(token: string, page: PageSurface): Promise<void> {
|
|
79
79
|
let ok = false
|
|
80
80
|
try {
|
|
@@ -91,9 +91,15 @@ async function runAccept(token: string, page: PageSurface): Promise<void> {
|
|
|
91
91
|
page.replaceState(`${url.pathname}${url.search}${url.hash}`)
|
|
92
92
|
if (ok) {
|
|
93
93
|
// Phones land on the standalone simplified surface (the full desktop UI
|
|
94
|
-
// is not built for small screens)
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
// is not built for small screens). Keep the workspace target so the
|
|
95
|
+
// mobile surface can open the intended workspace instead of losing the
|
|
96
|
+
// QR context at this navigation boundary.
|
|
97
|
+
if (isMobileSurface()) {
|
|
98
|
+
url.pathname = '/m'
|
|
99
|
+
page.navigate(`${url.pathname}${url.search}${url.hash}`)
|
|
100
|
+
} else {
|
|
101
|
+
page.reload()
|
|
102
|
+
}
|
|
97
103
|
}
|
|
98
104
|
}
|
|
99
105
|
|
package/src/client/locales.ts
CHANGED
|
@@ -55,6 +55,8 @@ export const zh = {
|
|
|
55
55
|
'settings.publicBaseUrlHint': '内网穿透的公网地址,如 Cloudflare Tunnel:https://xxx.trycloudflare.com。填写后二维码将生成公网链接,手机不在同一网络也能配对;留空则仅局域网可用。',
|
|
56
56
|
'settings.autoTunnel': '自动公网隧道',
|
|
57
57
|
'settings.autoTunnelHint': '开启后插件自动启动 Cloudflare quick tunnel(无需安装任何工具),并自动更新公网地址与信任配置,手机随时可用公网配对;关闭时使用上方手动填写的公网地址。',
|
|
58
|
+
'settings.mobileEnterToSend': '移动端回车发送',
|
|
59
|
+
'settings.mobileEnterToSendHint': '开启:移动端输入框按 Enter 直接发送,Shift+Enter 换行;关闭:Enter 换行,仅点发送按钮发送。',
|
|
58
60
|
'settings.inherit': '继承',
|
|
59
61
|
'settings.on': '开',
|
|
60
62
|
'settings.off': '关',
|
|
@@ -89,11 +91,13 @@ export const zh = {
|
|
|
89
91
|
'update.missingDetail': '无法定位已安装的 dsh-web-ui 聚合包。请先运行 dsh plugin --profile <profile> add @linxin666/dsh-web-ui-all。',
|
|
90
92
|
'update.offline': '无法连接更新源',
|
|
91
93
|
'update.offlineDetail': '网络不可用或更新服务异常,请稍后重试。',
|
|
94
|
+
'update.unmounted': '更新服务未加载',
|
|
95
|
+
'update.unmountedDetail': '当前 dsh web 进程运行的插件版本不含更新服务,请重启 dsh web(Ctrl+C 后重新运行)后再试。',
|
|
92
96
|
'update.error': '更新失败',
|
|
93
97
|
'update.packages': '组件版本',
|
|
94
98
|
'update.retry': '重新检查',
|
|
95
99
|
'update.close': '关闭更新面板',
|
|
96
|
-
'update.error.pnpmMissing': '未找到 pnpm
|
|
100
|
+
'update.error.pnpmMissing': '未找到 pnpm,已尝试 pnpm / corepack / npx 仍不可用。请安装 pnpm 后重启应用(Windows: npm install -g pnpm 或 corepack enable;macOS: brew install pnpm 或 corepack enable)。',
|
|
97
101
|
'update.error.timeout': '更新超时,已终止安装进程,请重试。',
|
|
98
102
|
'update.error.notFound': '未找到 dsh-web-ui 聚合包安装,无法更新。',
|
|
99
103
|
'update.error.link': '当前为本地开发模式(链接安装),无法自动更新。',
|
|
@@ -162,6 +166,8 @@ export const en = {
|
|
|
162
166
|
'settings.publicBaseUrlHint': 'The public URL of a tunnel in front of this server, e.g. a Cloudflare Tunnel: https://xxx.trycloudflare.com. When set, the QR link is built from it so a phone anywhere can pair; leave blank for LAN-only usage.',
|
|
163
167
|
'settings.autoTunnel': 'Auto public tunnel',
|
|
164
168
|
'settings.autoTunnelHint': 'When on, the plugin runs its own Cloudflare quick tunnel (no tool installation needed) and keeps the public address and trust config in sync automatically, so a phone anywhere can pair at any time; when off, the manually entered public address above applies.',
|
|
169
|
+
'settings.mobileEnterToSend': 'Enter to send on mobile',
|
|
170
|
+
'settings.mobileEnterToSendHint': 'On: Enter in the mobile composer sends immediately and Shift+Enter inserts a newline. Off: Enter inserts a newline and only the send button sends.',
|
|
165
171
|
'settings.inherit': 'Inherit',
|
|
166
172
|
'settings.on': 'On',
|
|
167
173
|
'settings.off': 'Off',
|
|
@@ -196,11 +202,13 @@ export const en = {
|
|
|
196
202
|
'update.missingDetail': 'Could not locate the installed dsh-web-ui aggregate package. Install it with: dsh plugin --profile <profile> add @linxin666/dsh-web-ui-all',
|
|
197
203
|
'update.offline': 'Cannot reach the update source',
|
|
198
204
|
'update.offlineDetail': 'The network is unavailable or the update service is failing; try again later.',
|
|
205
|
+
'update.unmounted': 'The update service is not loaded',
|
|
206
|
+
'update.unmountedDetail': 'The running dsh web process loads a plugin build without the update service; restart dsh web (Ctrl+C, then run dsh web again) and retry.',
|
|
199
207
|
'update.error': 'Update failed',
|
|
200
208
|
'update.packages': 'Component versions',
|
|
201
209
|
'update.retry': 'Check again',
|
|
202
210
|
'update.close': 'Close update panel',
|
|
203
|
-
'update.error.pnpmMissing': 'pnpm was not found. Install
|
|
211
|
+
'update.error.pnpmMissing': 'pnpm was not found (tried pnpm / corepack / npx). Install pnpm and restart the app (Windows: npm install -g pnpm or corepack enable; macOS: brew install pnpm or corepack enable).',
|
|
204
212
|
'update.error.timeout': 'The update timed out and the install process was killed; try again.',
|
|
205
213
|
'update.error.notFound': 'The dsh-web-ui aggregate package is not installed; nothing to update.',
|
|
206
214
|
'update.error.link': 'Local development mode (link install) — automatic update is unavailable.',
|