@linxin666/dsh-remote-web-ui 0.1.20 → 0.2.1
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 +2 -2
- package/README.md +56 -26
- package/README.zh.md +20 -15
- package/assets/mobile-icon-192.png +0 -0
- package/assets/mobile-icon-512.png +0 -0
- package/assets/mobile-service-worker.js +64 -0
- package/lib/client.js +272 -47
- package/lib/client.js.map +1 -1
- package/lib/index.js +316 -60
- package/lib/mobile.js +487 -56
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts +28 -0
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +147 -6
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +13 -2
- package/lib/types/client/RemoteSettingsCard.d.ts +2 -2
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +1 -1
- package/lib/types/client/UpdateEntry.d.ts.map +1 -1
- package/lib/types/client/UpdateEntry.js +36 -3
- package/lib/types/client/UpdatePanel.d.ts.map +1 -1
- package/lib/types/client/UpdatePanel.js +2 -2
- package/lib/types/client/deep-link.js +1 -1
- package/lib/types/client/locales.d.ts +4 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +4 -0
- package/lib/types/gate.d.ts +9 -0
- package/lib/types/gate.d.ts.map +1 -1
- package/lib/types/gate.js +17 -4
- package/lib/types/index.d.ts +25 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +30 -13
- package/lib/types/mobile/PairRequiredView.d.ts +7 -0
- package/lib/types/mobile/PairRequiredView.d.ts.map +1 -0
- package/lib/types/mobile/PairRequiredView.js +28 -0
- package/lib/types/mobile/PairRequiredView.test.d.ts +2 -0
- package/lib/types/mobile/PairRequiredView.test.d.ts.map +1 -0
- package/lib/types/mobile/PairRequiredView.test.js +24 -0
- package/lib/types/mobile/api.d.ts +10 -0
- package/lib/types/mobile/api.d.ts.map +1 -1
- package/lib/types/mobile/api.js +4 -0
- package/lib/types/mobile/index.js +14 -1
- package/lib/types/mobile/messages.d.ts +24 -0
- package/lib/types/mobile/messages.d.ts.map +1 -1
- package/lib/types/mobile/messages.js +60 -23
- package/lib/types/mobile/messages.test.js +49 -1
- 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 +86 -0
- package/lib/types/mobile/pairing.d.ts +31 -0
- package/lib/types/mobile/pairing.d.ts.map +1 -0
- package/lib/types/mobile/pairing.js +62 -0
- package/lib/types/mobile/views/App.d.ts +12 -5
- package/lib/types/mobile/views/App.d.ts.map +1 -1
- package/lib/types/mobile/views/App.js +34 -7
- package/lib/types/mobile/views/App.test.d.ts +2 -0
- package/lib/types/mobile/views/App.test.d.ts.map +1 -0
- package/lib/types/mobile/views/App.test.js +52 -0
- package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
- package/lib/types/mobile/views/ChatView.js +34 -11
- package/lib/types/mobile/views/SessionListView.d.ts.map +1 -1
- package/lib/types/mobile/views/SessionListView.js +30 -4
- package/lib/types/mobile/views/SessionListView.test.js +58 -1
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +1 -1
- package/lib/types/mobile/views/WorkspaceView.js +4 -2
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +10 -1
- package/lib/types/mobile-pwa.d.ts +6 -0
- package/lib/types/mobile-pwa.d.ts.map +1 -0
- package/lib/types/mobile-pwa.js +17 -0
- package/lib/types/mobile-routes.d.ts +5 -8
- package/lib/types/mobile-routes.d.ts.map +1 -1
- package/lib/types/mobile-routes.js +115 -30
- package/lib/types/pairing-access.d.ts +28 -0
- package/lib/types/pairing-access.d.ts.map +1 -0
- package/lib/types/pairing-access.js +26 -0
- package/lib/types/pairing.d.ts +27 -0
- package/lib/types/pairing.d.ts.map +1 -1
- package/lib/types/pairing.js +68 -0
- package/lib/types/pairing.persist.test.d.ts +2 -0
- package/lib/types/pairing.persist.test.d.ts.map +1 -0
- package/lib/types/pairing.persist.test.js +128 -0
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +23 -3
- package/lib/types/tunnel.d.ts +1 -0
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +7 -2
- package/lib/types/update.d.ts +4 -4
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +35 -12
- package/package.json +12 -12
- package/src/client/PluginSettingsCard.tsx +229 -18
- package/src/client/RemoteEntry.tsx +12 -2
- package/src/client/RemoteSettingsCard.tsx +3 -2
- package/src/client/UpdateEntry.tsx +36 -4
- package/src/client/UpdatePanel.tsx +2 -0
- package/src/client/deep-link.ts +1 -1
- package/src/client/locales.ts +4 -0
- package/src/client/remote.module.css +26 -0
- package/src/client/settings-card.module.css +90 -1
- package/src/gate.ts +17 -3
- package/src/index.ts +46 -15
- package/src/mobile/PairRequiredView.test.tsx +28 -0
- package/src/mobile/PairRequiredView.tsx +58 -0
- package/src/mobile/api.ts +14 -1
- package/src/mobile/index.tsx +15 -1
- package/src/mobile/messages.test.ts +52 -1
- package/src/mobile/messages.ts +65 -22
- package/src/mobile/mobile-styles.ts +86 -0
- package/src/mobile/pairing.ts +77 -0
- package/src/mobile/views/App.test.tsx +62 -0
- package/src/mobile/views/App.tsx +53 -6
- package/src/mobile/views/ChatView.tsx +33 -10
- package/src/mobile/views/SessionListView.test.tsx +69 -1
- package/src/mobile/views/SessionListView.tsx +58 -5
- package/src/mobile/views/WorkspaceView.tsx +4 -2
- package/src/mobile-api.ts +9 -1
- package/src/mobile-pwa.ts +19 -0
- package/src/mobile-routes.ts +123 -30
- package/src/pairing-access.ts +36 -0
- package/src/pairing.persist.test.ts +145 -0
- package/src/pairing.ts +70 -1
- package/src/routes.ts +23 -3
- package/src/tunnel.ts +7 -2
- package/src/update.ts +42 -14
package/lib/mobile.js
CHANGED
|
@@ -7578,6 +7578,10 @@ async function fetchMobilePreferences() {
|
|
|
7578
7578
|
async function listSessions(cursor) {
|
|
7579
7579
|
return await callUnary("session.list", cursor === void 0 ? {} : { cursor });
|
|
7580
7580
|
}
|
|
7581
|
+
/** Read the available agent compositions for a new session. */
|
|
7582
|
+
async function listAgentPresets() {
|
|
7583
|
+
return await callUnary("agentPreset.list", {});
|
|
7584
|
+
}
|
|
7581
7585
|
/**
|
|
7582
7586
|
* Create a blank session (entity birth precedes the first message). Name a
|
|
7583
7587
|
* workspace to attach it there, or a cwd; omitting both uses the host cwd.
|
|
@@ -11958,7 +11962,7 @@ function superRefine(fn, params) {
|
|
|
11958
11962
|
return /* @__PURE__ */ _superRefine(fn, params);
|
|
11959
11963
|
}
|
|
11960
11964
|
//#endregion
|
|
11961
|
-
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.
|
|
11965
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.7_36988429fa7c61ceeac966dddad3597f/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/rpc.schema.js
|
|
11962
11966
|
/**
|
|
11963
11967
|
* Message-layer zod schemas: the four wire full forms + error body +
|
|
11964
11968
|
* carrier receipt. The payload slot is unknown in the full-form schemas — business payloads
|
|
@@ -12139,11 +12143,6 @@ const rpcErrorSchema = discriminatedUnion("code", [
|
|
|
12139
12143
|
message: string(),
|
|
12140
12144
|
details: object({ ns: string() })
|
|
12141
12145
|
}),
|
|
12142
|
-
object({
|
|
12143
|
-
code: literal("settings-not-exposed"),
|
|
12144
|
-
message: string(),
|
|
12145
|
-
details: object({ ns: string() })
|
|
12146
|
-
}),
|
|
12147
12146
|
object({
|
|
12148
12147
|
code: literal("settings-conflict"),
|
|
12149
12148
|
message: string(),
|
|
@@ -12272,7 +12271,7 @@ union([object({ accepted: literal(true) }), object({
|
|
|
12272
12271
|
reason: union([literal("not-pending"), literal("bad-response")])
|
|
12273
12272
|
})]);
|
|
12274
12273
|
//#endregion
|
|
12275
|
-
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.
|
|
12274
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.7_36988429fa7c61ceeac966dddad3597f/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/session-search.js
|
|
12276
12275
|
/**
|
|
12277
12276
|
* Return the longest prefix containing at most `maximum` Unicode code points.
|
|
12278
12277
|
* @param value - text to bound.
|
|
@@ -12290,7 +12289,7 @@ function truncateUnicodeCodePoints(value, maximum) {
|
|
|
12290
12289
|
return value;
|
|
12291
12290
|
}
|
|
12292
12291
|
//#endregion
|
|
12293
|
-
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.
|
|
12292
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.7_36988429fa7c61ceeac966dddad3597f/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/sessions.schema.js
|
|
12294
12293
|
/**
|
|
12295
12294
|
* sessions domain zod schemas (names derived from map keys: sessionListRequestSchema /
|
|
12296
12295
|
* sessionListValueSchema). SessionEvent passthrough = strict envelope (type/seq/time) + wide
|
|
@@ -12526,7 +12525,7 @@ object({ accepted: literal(true) });
|
|
|
12526
12525
|
object({ sessionId: sessionIdSchema });
|
|
12527
12526
|
object({ accepted: literal(true) });
|
|
12528
12527
|
//#endregion
|
|
12529
|
-
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.
|
|
12528
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.7_36988429fa7c61ceeac966dddad3597f/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/approvals.schema.js
|
|
12530
12529
|
/**
|
|
12531
12530
|
* approvals domain zod schemas (respond is a client-response; the payload schema serves
|
|
12532
12531
|
* the /api/respond endpoint's second parse after routing via the pending table).
|
|
@@ -12560,7 +12559,7 @@ const taskViewSchema = object({
|
|
|
12560
12559
|
finishedAt: number().int().nonnegative().optional()
|
|
12561
12560
|
});
|
|
12562
12561
|
//#endregion
|
|
12563
|
-
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.
|
|
12562
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.7_36988429fa7c61ceeac966dddad3597f/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/workspace.schema.js
|
|
12564
12563
|
/**
|
|
12565
12564
|
* workspace domain zod schemas (names derived from map keys). The
|
|
12566
12565
|
* WorkspaceId brand cast lives in sessions.schema (see the note there) and
|
|
@@ -12606,7 +12605,7 @@ object({ workspace: workspaceViewSchema });
|
|
|
12606
12605
|
object({ sessionId: sessionIdSchema });
|
|
12607
12606
|
object({ archivedSessionIds: array(sessionIdSchema) });
|
|
12608
12607
|
//#endregion
|
|
12609
|
-
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.
|
|
12608
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-host-apiproxy@0.1.0-rc.7_36988429fa7c61ceeac966dddad3597f/node_modules/@deepseek-ai/dsh-host-apiproxy/lib/types/api/events.schema.js
|
|
12610
12609
|
/**
|
|
12611
12610
|
* events domain zod schemas: MuxFrame / HostFrame unions (discriminatedUnion('type')).
|
|
12612
12611
|
* A frame is the payload slot of the ServerRequest full form; the SessionEvent inside
|
|
@@ -13057,13 +13056,6 @@ function chunkTarget(data) {
|
|
|
13057
13056
|
if (step !== void 0) result.step = step;
|
|
13058
13057
|
return result;
|
|
13059
13058
|
}
|
|
13060
|
-
/** Max message seq across an existing list, or -1 for an empty list. */
|
|
13061
|
-
function watermarkOf(existing) {
|
|
13062
|
-
if (existing === void 0) return -1;
|
|
13063
|
-
let maxSeq = -1;
|
|
13064
|
-
for (const message of existing) if (message.seq > maxSeq) maxSeq = message.seq;
|
|
13065
|
-
return maxSeq;
|
|
13066
|
-
}
|
|
13067
13059
|
function createState(existing) {
|
|
13068
13060
|
const messages = existing === void 0 ? [] : [...existing];
|
|
13069
13061
|
const state = {
|
|
@@ -13072,9 +13064,11 @@ function createState(existing) {
|
|
|
13072
13064
|
pendingByTurnStep: /* @__PURE__ */ new Map(),
|
|
13073
13065
|
turnStepMessage: /* @__PURE__ */ new Map(),
|
|
13074
13066
|
messageTurn: /* @__PURE__ */ new Map(),
|
|
13075
|
-
toolNames: /* @__PURE__ */ new Map()
|
|
13067
|
+
toolNames: /* @__PURE__ */ new Map(),
|
|
13068
|
+
maxSeq: -1
|
|
13076
13069
|
};
|
|
13077
13070
|
for (const message of messages) {
|
|
13071
|
+
if (message.seq > state.maxSeq) state.maxSeq = message.seq;
|
|
13078
13072
|
state.byId.set(message.id, message);
|
|
13079
13073
|
if (message.kind !== "assistant") continue;
|
|
13080
13074
|
const decoded = decodePendingTurnStep(message.id);
|
|
@@ -13128,6 +13122,7 @@ function retargetTurnStep(state, key, oldMessage, next) {
|
|
|
13128
13122
|
}
|
|
13129
13123
|
/** Fold one event into the working state. Assumes the event passes the watermark. */
|
|
13130
13124
|
function applyEvent(state, event) {
|
|
13125
|
+
if (event.seq > state.maxSeq) state.maxSeq = event.seq;
|
|
13131
13126
|
switch (event.type) {
|
|
13132
13127
|
case "user/message":
|
|
13133
13128
|
applyUserMessage(state, event);
|
|
@@ -13422,14 +13417,58 @@ function applyTurnEnd(state, event) {
|
|
|
13422
13417
|
* @returns messages sorted by seq.
|
|
13423
13418
|
*/
|
|
13424
13419
|
function foldEvents(events, existing) {
|
|
13425
|
-
|
|
13426
|
-
|
|
13427
|
-
|
|
13428
|
-
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
|
|
13432
|
-
|
|
13420
|
+
return new EventFolder(existing).fold(events);
|
|
13421
|
+
}
|
|
13422
|
+
/**
|
|
13423
|
+
* Incremental folder for one message stream. Live chat folds one event at a
|
|
13424
|
+
* time; rebuilding the five index maps by scanning every message per event
|
|
13425
|
+
* made that path O(n) per event (O(n * events) per turn). A folder keeps the
|
|
13426
|
+
* indexes alive across folds, applies each event in O(1) map operations, and
|
|
13427
|
+
* returns the previous snapshot identity unchanged when nothing applied, so
|
|
13428
|
+
* React skips the re-render entirely. Replayed events are no-ops: the maxSeq
|
|
13429
|
+
* watermark advanced by the first application skips them, which also makes a
|
|
13430
|
+
* double-invoked React state updater harmless.
|
|
13431
|
+
*/
|
|
13432
|
+
var EventFolder = class {
|
|
13433
|
+
state;
|
|
13434
|
+
snapshotList;
|
|
13435
|
+
/** @param initial - seed rows (history tail load); omit for an empty stream. */
|
|
13436
|
+
constructor(initial) {
|
|
13437
|
+
this.state = createState(initial);
|
|
13438
|
+
}
|
|
13439
|
+
/** Fold one batch incrementally; returns the current snapshot list. */
|
|
13440
|
+
fold(events) {
|
|
13441
|
+
const sorted = [...events].sort((a, b) => a.seq - b.seq);
|
|
13442
|
+
let applied = false;
|
|
13443
|
+
for (const event of sorted) {
|
|
13444
|
+
if (event.seq <= this.state.maxSeq) continue;
|
|
13445
|
+
applyEvent(this.state, event);
|
|
13446
|
+
applied = true;
|
|
13447
|
+
}
|
|
13448
|
+
if (!applied && this.snapshotList !== void 0) return this.snapshotList;
|
|
13449
|
+
this.snapshotList = snapshotOf(this.state);
|
|
13450
|
+
return this.snapshotList;
|
|
13451
|
+
}
|
|
13452
|
+
/** Replace the whole stream (history reload / session switch). */
|
|
13453
|
+
seed(messages) {
|
|
13454
|
+
this.state = createState(messages);
|
|
13455
|
+
this.snapshotList = void 0;
|
|
13456
|
+
}
|
|
13457
|
+
/** Prepend an older history page (exact seam; no overlapping seqs). */
|
|
13458
|
+
prepend(older) {
|
|
13459
|
+
this.state = createState([...older, ...this.state.messages]);
|
|
13460
|
+
this.snapshotList = void 0;
|
|
13461
|
+
}
|
|
13462
|
+
/** Current snapshot list; a fresh copy whenever the folder changed. */
|
|
13463
|
+
snapshot() {
|
|
13464
|
+
if (this.snapshotList !== void 0) return this.snapshotList;
|
|
13465
|
+
this.snapshotList = snapshotOf(this.state);
|
|
13466
|
+
return this.snapshotList;
|
|
13467
|
+
}
|
|
13468
|
+
};
|
|
13469
|
+
/** Copy the folder's rows and keep them seq-ordered (skips re-sorting the common ordered case). */
|
|
13470
|
+
function snapshotOf(state) {
|
|
13471
|
+
const out = [...state.messages];
|
|
13433
13472
|
let ordered = true;
|
|
13434
13473
|
for (let index = 1; index < out.length; index += 1) {
|
|
13435
13474
|
const prev = out[index - 1];
|
|
@@ -13439,7 +13478,7 @@ function foldEvents(events, existing) {
|
|
|
13439
13478
|
break;
|
|
13440
13479
|
}
|
|
13441
13480
|
}
|
|
13442
|
-
return ordered ? out :
|
|
13481
|
+
return ordered ? out : out.sort((a, b) => a.seq - b.seq || (a.id < b.id ? -1 : 1));
|
|
13443
13482
|
}
|
|
13444
13483
|
//#endregion
|
|
13445
13484
|
//#region src/mobile/markdown.ts
|
|
@@ -13916,6 +13955,8 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13916
13955
|
const tailLoadingRef = (0, import_react.useRef)(true);
|
|
13917
13956
|
/** Live session events buffered while the initial tail page loads. */
|
|
13918
13957
|
const liveBufferRef = (0, import_react.useRef)([]);
|
|
13958
|
+
/** Incremental folder for this session's stream (indexes stay hot across events). */
|
|
13959
|
+
const folderRef = (0, import_react.useRef)(void 0);
|
|
13919
13960
|
/** True once the live buffer hit its cap (oldest events were dropped). */
|
|
13920
13961
|
const liveBufferOverflowRef = (0, import_react.useRef)(false);
|
|
13921
13962
|
/** The session's permission select (absent = capability not composed). */
|
|
@@ -13951,6 +13992,7 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13951
13992
|
tailLoadingRef.current = true;
|
|
13952
13993
|
liveBufferRef.current = [];
|
|
13953
13994
|
liveBufferOverflowRef.current = false;
|
|
13995
|
+
folderRef.current = void 0;
|
|
13954
13996
|
setLoading(true);
|
|
13955
13997
|
setError(void 0);
|
|
13956
13998
|
setMessages([]);
|
|
@@ -13959,14 +14001,18 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13959
14001
|
const buffered = liveBufferRef.current;
|
|
13960
14002
|
liveBufferRef.current = [];
|
|
13961
14003
|
tailLoadingRef.current = false;
|
|
13962
|
-
|
|
14004
|
+
const folder = new EventFolder(foldEvents(page.events.map(eventOf)));
|
|
14005
|
+
folderRef.current = folder;
|
|
14006
|
+
setMessages(folder.fold(buffered));
|
|
13963
14007
|
setHasOlder(page.hasMore);
|
|
13964
14008
|
setLoading(false);
|
|
13965
14009
|
const projections = page.projections?.values;
|
|
13966
14010
|
setPermissions(parsePermissionSelect(projections?.["permissions"]));
|
|
13967
14011
|
if (liveBufferOverflowRef.current) loadHistory(session.sessionId, void 0, controller.signal).then((fresh) => {
|
|
13968
14012
|
if (cancelled) return;
|
|
13969
|
-
|
|
14013
|
+
const folder = folderRef.current;
|
|
14014
|
+
const freshEvents = fresh.events.map(eventOf);
|
|
14015
|
+
setMessages((previous) => folder === void 0 ? foldEvents(freshEvents, previous) : folder.fold(freshEvents));
|
|
13970
14016
|
liveBufferOverflowRef.current = false;
|
|
13971
14017
|
}, (reason) => {
|
|
13972
14018
|
if (cancelled) return;
|
|
@@ -13977,7 +14023,10 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13977
14023
|
const buffered = liveBufferRef.current;
|
|
13978
14024
|
liveBufferRef.current = [];
|
|
13979
14025
|
tailLoadingRef.current = false;
|
|
13980
|
-
if (buffered.length > 0)
|
|
14026
|
+
if (buffered.length > 0) {
|
|
14027
|
+
const folder = folderRef.current;
|
|
14028
|
+
setMessages(folder === void 0 ? foldEvents(buffered) : folder.fold(buffered));
|
|
14029
|
+
}
|
|
13981
14030
|
setError(errorText(reason));
|
|
13982
14031
|
setLoading(false);
|
|
13983
14032
|
});
|
|
@@ -14007,7 +14056,10 @@ function ChatView({ session, mux, onBack }) {
|
|
|
14007
14056
|
liveBufferRef.current.push(event);
|
|
14008
14057
|
return;
|
|
14009
14058
|
}
|
|
14010
|
-
setMessages((previous) =>
|
|
14059
|
+
setMessages((previous) => {
|
|
14060
|
+
const folder = folderRef.current;
|
|
14061
|
+
return folder === void 0 ? foldEvents([event], previous) : folder.fold([event]);
|
|
14062
|
+
});
|
|
14011
14063
|
return;
|
|
14012
14064
|
}
|
|
14013
14065
|
if (frame.type === "session/projection" && frame.sessionId === session.sessionId && frame.key === "permissions") setPermissions(parsePermissionSelect(frame.value));
|
|
@@ -14044,7 +14096,12 @@ function ChatView({ session, mux, onBack }) {
|
|
|
14044
14096
|
pendingRef.current = false;
|
|
14045
14097
|
setLoading(false);
|
|
14046
14098
|
const older = foldEvents(page.events.map(eventOf));
|
|
14047
|
-
|
|
14099
|
+
const folder = folderRef.current;
|
|
14100
|
+
if (folder === void 0) setMessages((previous) => [...older, ...previous]);
|
|
14101
|
+
else {
|
|
14102
|
+
folder.prepend(older);
|
|
14103
|
+
setMessages(folder.snapshot());
|
|
14104
|
+
}
|
|
14048
14105
|
setHasOlder(page.hasMore);
|
|
14049
14106
|
}, (reason) => {
|
|
14050
14107
|
pendingRef.current = false;
|
|
@@ -14279,8 +14336,12 @@ function ChatView({ session, mux, onBack }) {
|
|
|
14279
14336
|
]
|
|
14280
14337
|
});
|
|
14281
14338
|
}
|
|
14282
|
-
/**
|
|
14283
|
-
|
|
14339
|
+
/**
|
|
14340
|
+
* One rendered message row (user bubble or assistant bubble with folds).
|
|
14341
|
+
* Memoized: live streaming updates exactly one message object per frame, so
|
|
14342
|
+
* unchanged rows skip re-rendering their markdown/sub-components.
|
|
14343
|
+
*/
|
|
14344
|
+
const MessageRow = (0, import_react.memo)(function MessageRow({ message, showToolCalls, showSystemMessages }) {
|
|
14284
14345
|
if (message.kind === "user" && message.sourceKind !== void 0 && message.sourceKind !== "user" && !showSystemMessages) return null;
|
|
14285
14346
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
14286
14347
|
className: `chat-msg chat-msg-${message.kind}${message.pending === true ? " chat-msg-pending" : ""}${message.failed === true ? " chat-msg-failed" : ""}`,
|
|
@@ -14301,7 +14362,7 @@ function MessageRow({ message, showToolCalls, showSystemMessages }) {
|
|
|
14301
14362
|
})
|
|
14302
14363
|
]
|
|
14303
14364
|
});
|
|
14304
|
-
}
|
|
14365
|
+
});
|
|
14305
14366
|
/** Collapsed-by-default reasoning disclosure (web-UI Think-row parity). */
|
|
14306
14367
|
function ReasoningDisclosure({ text, pending }) {
|
|
14307
14368
|
const [open, setOpen] = (0, import_react.useState)(false);
|
|
@@ -14853,6 +14914,9 @@ function SessionListView({ workspace, onBack, onPick }) {
|
|
|
14853
14914
|
const [error, setError] = (0, import_react.useState)(void 0);
|
|
14854
14915
|
const [creating, setCreating] = (0, import_react.useState)(false);
|
|
14855
14916
|
const [createError, setCreateError] = (0, import_react.useState)(void 0);
|
|
14917
|
+
const [presets, setPresets] = (0, import_react.useState)([]);
|
|
14918
|
+
const [selectedPreset, setSelectedPreset] = (0, import_react.useState)(void 0);
|
|
14919
|
+
const [presetsLoading, setPresetsLoading] = (0, import_react.useState)(true);
|
|
14856
14920
|
const cursorRef = (0, import_react.useRef)(void 0);
|
|
14857
14921
|
const busyRef = (0, import_react.useRef)(false);
|
|
14858
14922
|
(0, import_react.useEffect)(() => {
|
|
@@ -14875,6 +14939,25 @@ function SessionListView({ workspace, onBack, onPick }) {
|
|
|
14875
14939
|
cancelled = true;
|
|
14876
14940
|
};
|
|
14877
14941
|
}, [workspace]);
|
|
14942
|
+
(0, import_react.useEffect)(() => {
|
|
14943
|
+
let cancelled = false;
|
|
14944
|
+
setPresets([]);
|
|
14945
|
+
setSelectedPreset(void 0);
|
|
14946
|
+
setPresetsLoading(true);
|
|
14947
|
+
listAgentPresets().then((roster) => {
|
|
14948
|
+
if (cancelled) return;
|
|
14949
|
+
const usable = roster.presets.filter((preset) => preset.broken === void 0);
|
|
14950
|
+
setPresets(roster.presets);
|
|
14951
|
+
setSelectedPreset((usable.find((preset) => preset.isDefault) ?? usable[0])?.id);
|
|
14952
|
+
setPresetsLoading(false);
|
|
14953
|
+
}, () => {
|
|
14954
|
+
if (cancelled) return;
|
|
14955
|
+
setPresetsLoading(false);
|
|
14956
|
+
});
|
|
14957
|
+
return () => {
|
|
14958
|
+
cancelled = true;
|
|
14959
|
+
};
|
|
14960
|
+
}, [workspace]);
|
|
14878
14961
|
/** Pull the next page and append the rows that belong to this workspace. */
|
|
14879
14962
|
const loadMore = (0, import_react.useCallback)(() => {
|
|
14880
14963
|
if (busyRef.current) return;
|
|
@@ -14899,7 +14982,10 @@ function SessionListView({ workspace, onBack, onPick }) {
|
|
|
14899
14982
|
if (creating) return;
|
|
14900
14983
|
setCreating(true);
|
|
14901
14984
|
setCreateError(void 0);
|
|
14902
|
-
createSession({
|
|
14985
|
+
createSession({
|
|
14986
|
+
workspaceId: workspace.workspaceId,
|
|
14987
|
+
...selectedPreset !== void 0 ? { agentPreset: selectedPreset } : {}
|
|
14988
|
+
}).then((created) => {
|
|
14903
14989
|
setCreating(false);
|
|
14904
14990
|
const view = {
|
|
14905
14991
|
sessionId: created.sessionId,
|
|
@@ -14917,9 +15003,11 @@ function SessionListView({ workspace, onBack, onPick }) {
|
|
|
14917
15003
|
}, [
|
|
14918
15004
|
creating,
|
|
14919
15005
|
workspace,
|
|
14920
|
-
onPick
|
|
15006
|
+
onPick,
|
|
15007
|
+
selectedPreset
|
|
14921
15008
|
]);
|
|
14922
15009
|
const createHint = createError !== void 0 ? staleHostHint(createError) : void 0;
|
|
15010
|
+
const selectedPresetEntry = presets.find((preset) => preset.id === selectedPreset);
|
|
14923
15011
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
14924
15012
|
className: "mobile",
|
|
14925
15013
|
children: [
|
|
@@ -14944,17 +15032,50 @@ function SessionListView({ workspace, onBack, onPick }) {
|
|
|
14944
15032
|
className: "mobile-error mobile-pad",
|
|
14945
15033
|
children: error
|
|
14946
15034
|
}),
|
|
14947
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
14948
|
-
className: "mobile-pad",
|
|
14949
|
-
children:
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
15035
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
15036
|
+
className: "mobile-create mobile-pad",
|
|
15037
|
+
children: [
|
|
15038
|
+
presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
|
|
15039
|
+
className: "mobile-preset",
|
|
15040
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
15041
|
+
className: "mobile-presetLabel",
|
|
15042
|
+
children: "Agent 模式"
|
|
15043
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", {
|
|
15044
|
+
className: "mobile-presetSelect",
|
|
15045
|
+
value: selectedPreset ?? "",
|
|
15046
|
+
disabled: selectedPreset === void 0,
|
|
15047
|
+
onChange: (event) => {
|
|
15048
|
+
setSelectedPreset(event.target.value);
|
|
15049
|
+
},
|
|
15050
|
+
children: [selectedPreset === void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", {
|
|
15051
|
+
value: "",
|
|
15052
|
+
children: "无可用 Agent 模式"
|
|
15053
|
+
}), presets.map((preset) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("option", {
|
|
15054
|
+
value: preset.id,
|
|
15055
|
+
disabled: preset.broken !== void 0,
|
|
15056
|
+
children: [
|
|
15057
|
+
preset.name ?? preset.id,
|
|
15058
|
+
preset.isDefault ? "(默认)" : "",
|
|
15059
|
+
preset.trust === "user" ? "(本地)" : "",
|
|
15060
|
+
preset.broken !== void 0 ? "(不可用)" : ""
|
|
15061
|
+
]
|
|
15062
|
+
}, preset.id))]
|
|
15063
|
+
})]
|
|
15064
|
+
}),
|
|
15065
|
+
selectedPresetEntry?.description !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
15066
|
+
className: "mobile-presetDescription",
|
|
15067
|
+
children: selectedPresetEntry.description
|
|
15068
|
+
}),
|
|
15069
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
15070
|
+
type: "button",
|
|
15071
|
+
className: "mobile-new",
|
|
15072
|
+
disabled: creating || presetsLoading,
|
|
15073
|
+
onClick: () => {
|
|
15074
|
+
handleCreate();
|
|
15075
|
+
},
|
|
15076
|
+
children: creating ? "创建中…" : "+ 新建会话"
|
|
15077
|
+
})
|
|
15078
|
+
]
|
|
14958
15079
|
}),
|
|
14959
15080
|
createError !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
14960
15081
|
className: "mobile-error mobile-pad",
|
|
@@ -15027,6 +15148,7 @@ function SessionListView({ workspace, onBack, onPick }) {
|
|
|
15027
15148
|
function WorkspaceView({ initialWorkspaceId, onPick }) {
|
|
15028
15149
|
const [items, setItems] = (0, import_react.useState)(void 0);
|
|
15029
15150
|
const [error, setError] = (0, import_react.useState)(void 0);
|
|
15151
|
+
const [reload, setReload] = (0, import_react.useState)(0);
|
|
15030
15152
|
(0, import_react.useEffect)(() => {
|
|
15031
15153
|
let cancelled = false;
|
|
15032
15154
|
listWorkspaces().then((rows) => {
|
|
@@ -15044,7 +15166,11 @@ function WorkspaceView({ initialWorkspaceId, onPick }) {
|
|
|
15044
15166
|
return () => {
|
|
15045
15167
|
cancelled = true;
|
|
15046
15168
|
};
|
|
15047
|
-
}, [
|
|
15169
|
+
}, [
|
|
15170
|
+
initialWorkspaceId,
|
|
15171
|
+
onPick,
|
|
15172
|
+
reload
|
|
15173
|
+
]);
|
|
15048
15174
|
if (error !== void 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
15049
15175
|
className: "mobile",
|
|
15050
15176
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
|
|
@@ -15064,6 +15190,7 @@ function WorkspaceView({ initialWorkspaceId, onPick }) {
|
|
|
15064
15190
|
onClick: () => {
|
|
15065
15191
|
setError(void 0);
|
|
15066
15192
|
setItems(void 0);
|
|
15193
|
+
setReload((n) => n + 1);
|
|
15067
15194
|
},
|
|
15068
15195
|
children: "重试"
|
|
15069
15196
|
})]
|
|
@@ -15136,6 +15263,154 @@ function WorkspaceView({ initialWorkspaceId, onPick }) {
|
|
|
15136
15263
|
});
|
|
15137
15264
|
}
|
|
15138
15265
|
//#endregion
|
|
15266
|
+
//#region src/mobile/pairing.ts
|
|
15267
|
+
const MOBILE_ROOT = "/m/";
|
|
15268
|
+
/** Parse a pairing token or a copied pairing link without following its origin. */
|
|
15269
|
+
function parseMobilePairInput(value) {
|
|
15270
|
+
const trimmed = value.trim();
|
|
15271
|
+
if (trimmed === "") return void 0;
|
|
15272
|
+
try {
|
|
15273
|
+
const url = new URL(trimmed);
|
|
15274
|
+
const token = url.searchParams.get("pair");
|
|
15275
|
+
if (token === null || token === "") return void 0;
|
|
15276
|
+
const workspaceId = url.searchParams.get("workspace");
|
|
15277
|
+
return {
|
|
15278
|
+
token,
|
|
15279
|
+
...workspaceId !== null && workspaceId !== "" ? { workspaceId } : {}
|
|
15280
|
+
};
|
|
15281
|
+
} catch {
|
|
15282
|
+
return { token: trimmed };
|
|
15283
|
+
}
|
|
15284
|
+
}
|
|
15285
|
+
/** Build the safe, token-free mobile destination after successful pairing. */
|
|
15286
|
+
function mobilePairPath(workspaceId) {
|
|
15287
|
+
return workspaceId === void 0 ? MOBILE_ROOT : "/m/?workspace=" + encodeURIComponent(workspaceId);
|
|
15288
|
+
}
|
|
15289
|
+
/** Accept one pairing token on this exact browser or installed-web-app context. */
|
|
15290
|
+
async function acceptMobilePair(token, fetcher = fetch) {
|
|
15291
|
+
try {
|
|
15292
|
+
const response = await fetcher("/api/pair/accept", {
|
|
15293
|
+
method: "POST",
|
|
15294
|
+
headers: { "content-type": "application/json" },
|
|
15295
|
+
body: JSON.stringify({ token })
|
|
15296
|
+
});
|
|
15297
|
+
if (response.ok) return { ok: true };
|
|
15298
|
+
if (response.status === 404) return {
|
|
15299
|
+
ok: false,
|
|
15300
|
+
message: "配对链接无效或已过期。"
|
|
15301
|
+
};
|
|
15302
|
+
if (response.status === 409) return {
|
|
15303
|
+
ok: false,
|
|
15304
|
+
message: "配对链接已被使用。"
|
|
15305
|
+
};
|
|
15306
|
+
return {
|
|
15307
|
+
ok: false,
|
|
15308
|
+
message: "此设备无法使用该配对链接。"
|
|
15309
|
+
};
|
|
15310
|
+
} catch {
|
|
15311
|
+
return {
|
|
15312
|
+
ok: false,
|
|
15313
|
+
message: "无法连接到配对服务。"
|
|
15314
|
+
};
|
|
15315
|
+
}
|
|
15316
|
+
}
|
|
15317
|
+
/** Consume a QR pairing token before the mobile application starts making RPC calls. */
|
|
15318
|
+
async function consumeMobilePairUrl(href, fetcher = fetch) {
|
|
15319
|
+
let url;
|
|
15320
|
+
try {
|
|
15321
|
+
url = new URL(href);
|
|
15322
|
+
} catch {
|
|
15323
|
+
return { kind: "none" };
|
|
15324
|
+
}
|
|
15325
|
+
const token = url.searchParams.get("pair");
|
|
15326
|
+
if (token === null || token === "") return { kind: "none" };
|
|
15327
|
+
const workspaceId = url.searchParams.get("workspace");
|
|
15328
|
+
const path = mobilePairPath(workspaceId === null || workspaceId === "" ? void 0 : workspaceId);
|
|
15329
|
+
const result = await acceptMobilePair(token, fetcher);
|
|
15330
|
+
return result.ok ? {
|
|
15331
|
+
kind: "accepted",
|
|
15332
|
+
path
|
|
15333
|
+
} : {
|
|
15334
|
+
kind: "failed",
|
|
15335
|
+
path,
|
|
15336
|
+
message: result.message
|
|
15337
|
+
};
|
|
15338
|
+
}
|
|
15339
|
+
//#endregion
|
|
15340
|
+
//#region src/mobile/PairRequiredView.tsx
|
|
15341
|
+
/** Device-pairing gate for installed mobile web-app contexts with isolated storage. */
|
|
15342
|
+
/** Collect a fresh desktop-issued pairing link when this client has no paired cookie. */
|
|
15343
|
+
function PairRequiredView({ initialError, onPaired }) {
|
|
15344
|
+
const [value, setValue] = (0, import_react.useState)("");
|
|
15345
|
+
const [error, setError] = (0, import_react.useState)(initialError);
|
|
15346
|
+
const [submitting, setSubmitting] = (0, import_react.useState)(false);
|
|
15347
|
+
const submit = async (event) => {
|
|
15348
|
+
event.preventDefault();
|
|
15349
|
+
const input = parseMobilePairInput(value);
|
|
15350
|
+
if (input === void 0) {
|
|
15351
|
+
setError("请输入有效的配对链接。");
|
|
15352
|
+
return;
|
|
15353
|
+
}
|
|
15354
|
+
setSubmitting(true);
|
|
15355
|
+
setError(void 0);
|
|
15356
|
+
const result = await acceptMobilePair(input.token);
|
|
15357
|
+
setSubmitting(false);
|
|
15358
|
+
if (!result.ok) {
|
|
15359
|
+
setError(result.message);
|
|
15360
|
+
return;
|
|
15361
|
+
}
|
|
15362
|
+
onPaired(mobilePairPath(input.workspaceId));
|
|
15363
|
+
};
|
|
15364
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("main", {
|
|
15365
|
+
className: "mobile mobile-pair",
|
|
15366
|
+
"aria-labelledby": "mobile-pair-title",
|
|
15367
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
|
|
15368
|
+
className: "mobile-pairCard",
|
|
15369
|
+
onSubmit: (event) => {
|
|
15370
|
+
submit(event);
|
|
15371
|
+
},
|
|
15372
|
+
children: [
|
|
15373
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h1", {
|
|
15374
|
+
id: "mobile-pair-title",
|
|
15375
|
+
className: "mobile-title",
|
|
15376
|
+
children: "设备配对"
|
|
15377
|
+
}),
|
|
15378
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
15379
|
+
className: "mobile-muted",
|
|
15380
|
+
children: "粘贴桌面端复制的配对链接以连接此设备。"
|
|
15381
|
+
}),
|
|
15382
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", {
|
|
15383
|
+
className: "mobile-pairLabel",
|
|
15384
|
+
htmlFor: "mobile-pair-link",
|
|
15385
|
+
children: "配对链接"
|
|
15386
|
+
}),
|
|
15387
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
15388
|
+
id: "mobile-pair-link",
|
|
15389
|
+
className: "mobile-pairInput",
|
|
15390
|
+
value,
|
|
15391
|
+
onChange: (event) => {
|
|
15392
|
+
setValue(event.target.value);
|
|
15393
|
+
},
|
|
15394
|
+
autoComplete: "off",
|
|
15395
|
+
autoCapitalize: "off",
|
|
15396
|
+
spellCheck: false
|
|
15397
|
+
}),
|
|
15398
|
+
error === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
15399
|
+
className: "mobile-error",
|
|
15400
|
+
role: "alert",
|
|
15401
|
+
children: error
|
|
15402
|
+
}),
|
|
15403
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
15404
|
+
className: "mobile-new mobile-pairSubmit",
|
|
15405
|
+
type: "submit",
|
|
15406
|
+
disabled: submitting,
|
|
15407
|
+
children: submitting ? "正在配对" : "配对"
|
|
15408
|
+
})
|
|
15409
|
+
]
|
|
15410
|
+
})
|
|
15411
|
+
});
|
|
15412
|
+
}
|
|
15413
|
+
//#endregion
|
|
15139
15414
|
//#region src/mobile/views/App.tsx
|
|
15140
15415
|
/**
|
|
15141
15416
|
* Mobile surface root: the view state machine (workspaces → sessions →
|
|
@@ -15172,11 +15447,60 @@ function formatTime(epochMs) {
|
|
|
15172
15447
|
if (date.toDateString() === yesterday.toDateString()) return `昨天 ${clock}`;
|
|
15173
15448
|
return `${String(date.getMonth() + 1)}月${String(date.getDate())}日 ${clock}`;
|
|
15174
15449
|
}
|
|
15175
|
-
/**
|
|
15176
|
-
|
|
15177
|
-
|
|
15178
|
-
|
|
15179
|
-
|
|
15450
|
+
/** Whether the mobile gateway rejected this browser for lack of a paired cookie. */
|
|
15451
|
+
function isUnpairedMobileError(error) {
|
|
15452
|
+
return error instanceof RpcTransportError && error.message === "HTTP 403";
|
|
15453
|
+
}
|
|
15454
|
+
/** Classify an initial mobile preference failure without treating outage as authorization. */
|
|
15455
|
+
function mobilePairStateForError(error) {
|
|
15456
|
+
return isUnpairedMobileError(error) ? "unpaired" : "unavailable";
|
|
15457
|
+
}
|
|
15458
|
+
/** Gate the independent mobile bundle until its own browser context is paired. */
|
|
15459
|
+
function App({ initialPairError }) {
|
|
15460
|
+
const [pairState, setPairState] = (0, import_react.useState)("checking");
|
|
15461
|
+
(0, import_react.useEffect)(() => {
|
|
15462
|
+
let current = true;
|
|
15463
|
+
fetchMobilePreferences().then(() => {
|
|
15464
|
+
if (current) setPairState("paired");
|
|
15465
|
+
}, (error) => {
|
|
15466
|
+
if (current) setPairState(mobilePairStateForError(error));
|
|
15467
|
+
});
|
|
15468
|
+
return () => {
|
|
15469
|
+
current = false;
|
|
15470
|
+
};
|
|
15471
|
+
}, []);
|
|
15472
|
+
if (pairState === "checking") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("main", {
|
|
15473
|
+
className: "mobile mobile-empty",
|
|
15474
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
15475
|
+
className: "mobile-muted",
|
|
15476
|
+
children: "正在连接..."
|
|
15477
|
+
})
|
|
15478
|
+
});
|
|
15479
|
+
if (pairState === "unpaired") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PairRequiredView, {
|
|
15480
|
+
initialError: initialPairError,
|
|
15481
|
+
onPaired: (path) => {
|
|
15482
|
+
window.location.replace(path);
|
|
15483
|
+
}
|
|
15484
|
+
});
|
|
15485
|
+
if (pairState === "unavailable") return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", {
|
|
15486
|
+
className: "mobile mobile-empty",
|
|
15487
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
15488
|
+
className: "mobile-error",
|
|
15489
|
+
role: "alert",
|
|
15490
|
+
children: "无法连接到运行中的 DSH host。"
|
|
15491
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
15492
|
+
className: "mobile-new",
|
|
15493
|
+
type: "button",
|
|
15494
|
+
onClick: () => {
|
|
15495
|
+
window.location.reload();
|
|
15496
|
+
},
|
|
15497
|
+
children: "重试"
|
|
15498
|
+
})]
|
|
15499
|
+
});
|
|
15500
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PairedApp, {});
|
|
15501
|
+
}
|
|
15502
|
+
/** The existing remote mobile surface, mounted only after device pairing succeeds. */
|
|
15503
|
+
function PairedApp() {
|
|
15180
15504
|
const [route, setRoute] = (0, import_react.useState)({ kind: "workspaces" });
|
|
15181
15505
|
const [initialWorkspaceId, setInitialWorkspaceId] = (0, import_react.useState)(() => mobileWorkspaceTarget(window.location.search));
|
|
15182
15506
|
const muxRef = (0, import_react.useRef)(void 0);
|
|
@@ -15515,6 +15839,43 @@ body {
|
|
|
15515
15839
|
padding: 8px 16px;
|
|
15516
15840
|
}
|
|
15517
15841
|
|
|
15842
|
+
.mobile-create {
|
|
15843
|
+
display: grid;
|
|
15844
|
+
gap: 8px;
|
|
15845
|
+
}
|
|
15846
|
+
|
|
15847
|
+
.mobile-preset {
|
|
15848
|
+
display: grid;
|
|
15849
|
+
gap: 4px;
|
|
15850
|
+
}
|
|
15851
|
+
|
|
15852
|
+
.mobile-presetLabel {
|
|
15853
|
+
color: var(--m-text-secondary);
|
|
15854
|
+
font-size: 13px;
|
|
15855
|
+
font-weight: 600;
|
|
15856
|
+
}
|
|
15857
|
+
|
|
15858
|
+
.mobile-presetSelect {
|
|
15859
|
+
width: 100%;
|
|
15860
|
+
height: 40px;
|
|
15861
|
+
padding: 0 36px 0 12px;
|
|
15862
|
+
border: 1px solid var(--m-border);
|
|
15863
|
+
border-radius: 10px;
|
|
15864
|
+
background: var(--m-bg-input);
|
|
15865
|
+
color: var(--m-text);
|
|
15866
|
+
font: inherit;
|
|
15867
|
+
}
|
|
15868
|
+
|
|
15869
|
+
.mobile-presetSelect:disabled {
|
|
15870
|
+
opacity: 0.6;
|
|
15871
|
+
}
|
|
15872
|
+
|
|
15873
|
+
.mobile-presetDescription {
|
|
15874
|
+
margin: 0;
|
|
15875
|
+
color: var(--m-text-tertiary);
|
|
15876
|
+
font-size: 12px;
|
|
15877
|
+
}
|
|
15878
|
+
|
|
15518
15879
|
/* ── buttons ─────────────────────────────────────────────────────────── */
|
|
15519
15880
|
|
|
15520
15881
|
.mobile-button {
|
|
@@ -16159,6 +16520,7 @@ body {
|
|
|
16159
16520
|
.mobile-row:focus-visible,
|
|
16160
16521
|
.mobile-button:focus-visible,
|
|
16161
16522
|
.mobile-new:focus-visible,
|
|
16523
|
+
.mobile-presetSelect:focus-visible,
|
|
16162
16524
|
.chat-send:focus-visible,
|
|
16163
16525
|
.chat-chip:focus-visible,
|
|
16164
16526
|
.chat-msg-toggle:focus-visible,
|
|
@@ -16283,7 +16645,66 @@ body {
|
|
|
16283
16645
|
transition: none;
|
|
16284
16646
|
}
|
|
16285
16647
|
}
|
|
16648
|
+
|
|
16649
|
+
/* ── installed-app pairing ───────────────────────────────────────────── */
|
|
16650
|
+
|
|
16651
|
+
.mobile-pair {
|
|
16652
|
+
min-height: 100dvh;
|
|
16653
|
+
display: grid;
|
|
16654
|
+
place-items: center;
|
|
16655
|
+
padding: 24px;
|
|
16656
|
+
}
|
|
16657
|
+
|
|
16658
|
+
.mobile-pairCard {
|
|
16659
|
+
display: grid;
|
|
16660
|
+
width: min(100%, 420px);
|
|
16661
|
+
gap: 12px;
|
|
16662
|
+
padding: 24px;
|
|
16663
|
+
border: 1px solid var(--m-border);
|
|
16664
|
+
border-radius: 12px;
|
|
16665
|
+
background: var(--m-bg-raised);
|
|
16666
|
+
box-shadow: var(--m-shadow-raise);
|
|
16667
|
+
}
|
|
16668
|
+
|
|
16669
|
+
.mobile-pairLabel {
|
|
16670
|
+
color: var(--m-text-secondary);
|
|
16671
|
+
font-size: 13px;
|
|
16672
|
+
font-weight: 600;
|
|
16673
|
+
}
|
|
16674
|
+
|
|
16675
|
+
.mobile-pairInput {
|
|
16676
|
+
width: 100%;
|
|
16677
|
+
min-height: 42px;
|
|
16678
|
+
box-sizing: border-box;
|
|
16679
|
+
padding: 9px 12px;
|
|
16680
|
+
border: 1px solid var(--m-border);
|
|
16681
|
+
border-radius: 8px;
|
|
16682
|
+
background: var(--m-bg-input);
|
|
16683
|
+
color: var(--m-text);
|
|
16684
|
+
font: inherit;
|
|
16685
|
+
outline: none;
|
|
16686
|
+
}
|
|
16687
|
+
|
|
16688
|
+
.mobile-pairInput:focus-visible {
|
|
16689
|
+
border-color: var(--m-accent);
|
|
16690
|
+
box-shadow: 0 0 0 2px var(--m-bg), 0 0 0 4px var(--m-accent);
|
|
16691
|
+
}
|
|
16692
|
+
|
|
16693
|
+
.mobile-pairSubmit {
|
|
16694
|
+
width: 100%;
|
|
16695
|
+
}
|
|
16286
16696
|
`;
|
|
16697
|
+
const MOBILE_SERVICE_WORKER_URL = "/m/service-worker.js";
|
|
16698
|
+
/** Register the mobile shell worker when the current browser supports it. */
|
|
16699
|
+
async function registerMobilePwa(serviceWorker = typeof navigator === "undefined" ? void 0 : navigator.serviceWorker) {
|
|
16700
|
+
if (serviceWorker === void 0) return;
|
|
16701
|
+
try {
|
|
16702
|
+
await serviceWorker.register(MOBILE_SERVICE_WORKER_URL, {
|
|
16703
|
+
scope: "/m/",
|
|
16704
|
+
updateViaCache: "none"
|
|
16705
|
+
});
|
|
16706
|
+
} catch {}
|
|
16707
|
+
}
|
|
16287
16708
|
//#endregion
|
|
16288
16709
|
//#region src/mobile/index.tsx
|
|
16289
16710
|
/**
|
|
@@ -16301,7 +16722,17 @@ style.textContent = mobileCss;
|
|
|
16301
16722
|
document.head.appendChild(style);
|
|
16302
16723
|
const root = document.getElementById("root");
|
|
16303
16724
|
if (root === null) throw new Error("mobile: #root missing");
|
|
16304
|
-
(
|
|
16725
|
+
bootMobile(root);
|
|
16726
|
+
async function bootMobile(mount) {
|
|
16727
|
+
const pair = await consumeMobilePairUrl(window.location.href);
|
|
16728
|
+
if (pair.kind === "accepted") {
|
|
16729
|
+
window.location.replace(pair.path);
|
|
16730
|
+
return;
|
|
16731
|
+
}
|
|
16732
|
+
if (pair.kind === "failed") window.history.replaceState(null, "", pair.path);
|
|
16733
|
+
registerMobilePwa();
|
|
16734
|
+
(0, import_client.createRoot)(mount).render(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(App, { initialPairError: pair.kind === "failed" ? pair.message : void 0 }));
|
|
16735
|
+
}
|
|
16305
16736
|
//#endregion
|
|
16306
16737
|
|
|
16307
16738
|
//# sourceMappingURL=mobile.js.map
|