@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/mobile.js
CHANGED
|
@@ -7570,6 +7570,10 @@ async function listWorkspaces() {
|
|
|
7570
7570
|
const { items } = await callUnary("workspace.list", {});
|
|
7571
7571
|
return items;
|
|
7572
7572
|
}
|
|
7573
|
+
/** Read-only mobile display preferences (answered by the plugin, not the host proxy). */
|
|
7574
|
+
async function fetchMobilePreferences() {
|
|
7575
|
+
return await callUnary("mobile.preferences", {});
|
|
7576
|
+
}
|
|
7573
7577
|
/** One session.list page; omit the cursor for the first page. */
|
|
7574
7578
|
async function listSessions(cursor) {
|
|
7575
7579
|
return await callUnary("session.list", cursor === void 0 ? {} : { cursor });
|
|
@@ -7581,13 +7585,13 @@ async function listSessions(cursor) {
|
|
|
7581
7585
|
async function createSession(options = {}) {
|
|
7582
7586
|
return await callUnary("session.create", options);
|
|
7583
7587
|
}
|
|
7584
|
-
/** One history window; omit beforeSeq for the tail page. */
|
|
7585
|
-
async function history(sessionId, beforeSeq, maxMessages = 30) {
|
|
7588
|
+
/** One history window; omit beforeSeq for the tail page, pass a signal to abort. */
|
|
7589
|
+
async function history(sessionId, beforeSeq, maxMessages = 30, signal) {
|
|
7586
7590
|
return await callUnary("session.history", {
|
|
7587
7591
|
sessionId,
|
|
7588
7592
|
maxMessages,
|
|
7589
7593
|
...beforeSeq !== void 0 ? { beforeSeq } : {}
|
|
7590
|
-
});
|
|
7594
|
+
}, signal);
|
|
7591
7595
|
}
|
|
7592
7596
|
/** Send one text prompt (queued: the agent picks it up in order). */
|
|
7593
7597
|
async function prompt(sessionId, text) {
|
|
@@ -13526,20 +13530,6 @@ function ThemeToggle() {
|
|
|
13526
13530
|
children: dark ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SunIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MoonIcon, {})
|
|
13527
13531
|
});
|
|
13528
13532
|
}
|
|
13529
|
-
//#endregion
|
|
13530
|
-
//#region src/mobile/views/ChatView.tsx
|
|
13531
|
-
/**
|
|
13532
|
-
* Chat level: one session. Loads the history tail page on open, appends
|
|
13533
|
-
* pages upward (loadOlder), folds live mux frames in as they arrive, and
|
|
13534
|
-
* sends prompts through session.prompt.
|
|
13535
|
-
*
|
|
13536
|
-
* Rendering mirrors the desktop web UI's fold discipline on a small screen:
|
|
13537
|
-
* - reasoning text hides behind a collapsed "深度思考" disclosure,
|
|
13538
|
-
* - tool calls behind a collapsed tool disclosure (name + arguments),
|
|
13539
|
-
* - very long assistant text collapses with an explicit expand toggle,
|
|
13540
|
-
* - a toolbar above the composer carries the model (+ thinking effort) and
|
|
13541
|
-
* permission pickers, both as bottom sheets.
|
|
13542
|
-
*/
|
|
13543
13533
|
/** Extract the raw event from one history entry (the fold consumes events only). */
|
|
13544
13534
|
function eventOf(entry) {
|
|
13545
13535
|
return entry.event;
|
|
@@ -13597,26 +13587,74 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13597
13587
|
const [sending, setSending] = (0, import_react.useState)(false);
|
|
13598
13588
|
const scrollRef = (0, import_react.useRef)(void 0);
|
|
13599
13589
|
const pendingRef = (0, import_react.useRef)(false);
|
|
13590
|
+
/**
|
|
13591
|
+
* True while the initial tail page is in flight. Live events arriving in
|
|
13592
|
+
* that window go to {@link liveBufferRef} instead of the message list: the
|
|
13593
|
+
* tail load replaces the list wholesale, so a directly folded event would
|
|
13594
|
+
* flash once, be discarded by the snapshot, and then be skipped forever by
|
|
13595
|
+
* the seq watermark.
|
|
13596
|
+
*/
|
|
13597
|
+
const tailLoadingRef = (0, import_react.useRef)(true);
|
|
13598
|
+
/** Live session events buffered while the initial tail page loads. */
|
|
13599
|
+
const liveBufferRef = (0, import_react.useRef)([]);
|
|
13600
|
+
/** True once the live buffer hit its cap (oldest events were dropped). */
|
|
13601
|
+
const liveBufferOverflowRef = (0, import_react.useRef)(false);
|
|
13600
13602
|
/** The session's permission select (absent = capability not composed). */
|
|
13601
13603
|
const [permissions, setPermissions] = (0, import_react.useState)(void 0);
|
|
13602
13604
|
/** The current model selection for the toolbar chip (best-effort label). */
|
|
13603
13605
|
const [currentModel, setCurrentModel] = (0, import_react.useState)(void 0);
|
|
13604
13606
|
/** Which bottom sheet is open. */
|
|
13605
13607
|
const [sheet, setSheet] = (0, import_react.useState)(null);
|
|
13608
|
+
/**
|
|
13609
|
+
* Composer preference from the plugin's host settings (default true keeps
|
|
13610
|
+
* the legacy Enter-to-send behavior until the preference loads).
|
|
13611
|
+
*/
|
|
13612
|
+
const [mobileEnterToSend, setMobileEnterToSend] = (0, import_react.useState)(true);
|
|
13613
|
+
(0, import_react.useEffect)(() => {
|
|
13614
|
+
let cancelled = false;
|
|
13615
|
+
fetchMobilePreferences().then((preferences) => {
|
|
13616
|
+
if (!cancelled) setMobileEnterToSend(preferences.mobileEnterToSend !== false);
|
|
13617
|
+
}, () => {});
|
|
13618
|
+
return () => {
|
|
13619
|
+
cancelled = true;
|
|
13620
|
+
};
|
|
13621
|
+
}, []);
|
|
13606
13622
|
(0, import_react.useEffect)(() => {
|
|
13607
13623
|
let cancelled = false;
|
|
13624
|
+
const controller = new AbortController();
|
|
13625
|
+
const timeout = setTimeout(() => {
|
|
13626
|
+
controller.abort(new DOMException("history load timed out", "TimeoutError"));
|
|
13627
|
+
}, 15e3);
|
|
13628
|
+
tailLoadingRef.current = true;
|
|
13629
|
+
liveBufferRef.current = [];
|
|
13630
|
+
liveBufferOverflowRef.current = false;
|
|
13608
13631
|
setLoading(true);
|
|
13609
13632
|
setError(void 0);
|
|
13610
13633
|
setMessages([]);
|
|
13611
|
-
loadHistory(session.sessionId).then((page) => {
|
|
13634
|
+
loadHistory(session.sessionId, void 0, controller.signal).then((page) => {
|
|
13612
13635
|
if (cancelled) return;
|
|
13613
|
-
|
|
13636
|
+
const buffered = liveBufferRef.current;
|
|
13637
|
+
liveBufferRef.current = [];
|
|
13638
|
+
tailLoadingRef.current = false;
|
|
13639
|
+
setMessages(foldEvents(buffered, foldEvents(page.events.map(eventOf))));
|
|
13614
13640
|
setHasOlder(page.hasMore);
|
|
13615
13641
|
setLoading(false);
|
|
13616
13642
|
const projections = page.projections?.values;
|
|
13617
13643
|
setPermissions(parsePermissionSelect(projections?.["permissions"]));
|
|
13644
|
+
if (liveBufferOverflowRef.current) loadHistory(session.sessionId, void 0, controller.signal).then((fresh) => {
|
|
13645
|
+
if (cancelled) return;
|
|
13646
|
+
setMessages((previous) => foldEvents(fresh.events.map(eventOf), previous));
|
|
13647
|
+
liveBufferOverflowRef.current = false;
|
|
13648
|
+
}, (reason) => {
|
|
13649
|
+
if (cancelled) return;
|
|
13650
|
+
console.warn("history tail re-pull after live buffer overflow failed", reason);
|
|
13651
|
+
});
|
|
13618
13652
|
}, (reason) => {
|
|
13619
13653
|
if (cancelled) return;
|
|
13654
|
+
const buffered = liveBufferRef.current;
|
|
13655
|
+
liveBufferRef.current = [];
|
|
13656
|
+
tailLoadingRef.current = false;
|
|
13657
|
+
if (buffered.length > 0) setMessages(foldEvents(buffered));
|
|
13620
13658
|
setError(errorText(reason));
|
|
13621
13659
|
setLoading(false);
|
|
13622
13660
|
});
|
|
@@ -13625,6 +13663,8 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13625
13663
|
}, () => {});
|
|
13626
13664
|
return () => {
|
|
13627
13665
|
cancelled = true;
|
|
13666
|
+
clearTimeout(timeout);
|
|
13667
|
+
controller.abort();
|
|
13628
13668
|
};
|
|
13629
13669
|
}, [session.sessionId]);
|
|
13630
13670
|
(0, import_react.useEffect)(() => {
|
|
@@ -13632,7 +13672,19 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13632
13672
|
return mux.onFrame((frame) => {
|
|
13633
13673
|
if (frame.type === "session/event") {
|
|
13634
13674
|
if (frame.sessionId !== session.sessionId) return;
|
|
13635
|
-
|
|
13675
|
+
const event = frame.event;
|
|
13676
|
+
if (tailLoadingRef.current) {
|
|
13677
|
+
if (liveBufferRef.current.length >= 500) {
|
|
13678
|
+
liveBufferRef.current.shift();
|
|
13679
|
+
if (!liveBufferOverflowRef.current) {
|
|
13680
|
+
console.warn(`history tail is slow: live buffer reached 500 events; oldest buffered events will be re-fetched`);
|
|
13681
|
+
liveBufferOverflowRef.current = true;
|
|
13682
|
+
}
|
|
13683
|
+
}
|
|
13684
|
+
liveBufferRef.current.push(event);
|
|
13685
|
+
return;
|
|
13686
|
+
}
|
|
13687
|
+
setMessages((previous) => foldEvents([event], previous));
|
|
13636
13688
|
return;
|
|
13637
13689
|
}
|
|
13638
13690
|
if (frame.type === "session/projection" && frame.sessionId === session.sessionId && frame.key === "permissions") setPermissions(parsePermissionSelect(frame.value));
|
|
@@ -13800,13 +13852,13 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13800
13852
|
className: "chat-input",
|
|
13801
13853
|
rows: 1,
|
|
13802
13854
|
value: input,
|
|
13803
|
-
placeholder: "输入消息,Enter 发送…",
|
|
13804
|
-
enterKeyHint: "send",
|
|
13855
|
+
placeholder: mobileEnterToSend ? "输入消息,Enter 发送…" : "输入消息,Enter 换行,点按钮发送…",
|
|
13856
|
+
enterKeyHint: mobileEnterToSend ? "send" : "enter",
|
|
13805
13857
|
onChange: (event) => {
|
|
13806
13858
|
setInput(event.target.value);
|
|
13807
13859
|
},
|
|
13808
13860
|
onKeyDown: (event) => {
|
|
13809
|
-
if (event.key === "Enter" && !event.shiftKey) {
|
|
13861
|
+
if (mobileEnterToSend && event.key === "Enter" && !event.shiftKey) {
|
|
13810
13862
|
event.preventDefault();
|
|
13811
13863
|
send();
|
|
13812
13864
|
}
|
|
@@ -14503,13 +14555,18 @@ function SessionListView({ workspace, onBack, onPick }) {
|
|
|
14503
14555
|
* @param props - the pick action.
|
|
14504
14556
|
* @returns the roster.
|
|
14505
14557
|
*/
|
|
14506
|
-
function WorkspaceView({ onPick }) {
|
|
14558
|
+
function WorkspaceView({ initialWorkspaceId, onPick }) {
|
|
14507
14559
|
const [items, setItems] = (0, import_react.useState)(void 0);
|
|
14508
14560
|
const [error, setError] = (0, import_react.useState)(void 0);
|
|
14509
14561
|
(0, import_react.useEffect)(() => {
|
|
14510
14562
|
let cancelled = false;
|
|
14511
14563
|
listWorkspaces().then((rows) => {
|
|
14512
14564
|
if (cancelled) return;
|
|
14565
|
+
const target = initialWorkspaceId === void 0 ? void 0 : rows.find((workspace) => workspace.workspaceId === initialWorkspaceId);
|
|
14566
|
+
if (target !== void 0) {
|
|
14567
|
+
onPick(target);
|
|
14568
|
+
return;
|
|
14569
|
+
}
|
|
14513
14570
|
setItems(rows);
|
|
14514
14571
|
}, (reason) => {
|
|
14515
14572
|
if (cancelled) return;
|
|
@@ -14518,7 +14575,7 @@ function WorkspaceView({ onPick }) {
|
|
|
14518
14575
|
return () => {
|
|
14519
14576
|
cancelled = true;
|
|
14520
14577
|
};
|
|
14521
|
-
}, []);
|
|
14578
|
+
}, [initialWorkspaceId, onPick]);
|
|
14522
14579
|
if (error !== void 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
14523
14580
|
className: "mobile",
|
|
14524
14581
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
|
|
@@ -14617,6 +14674,11 @@ function WorkspaceView({ onPick }) {
|
|
|
14617
14674
|
* router, no state library: the surface is three fixed levels with a back
|
|
14618
14675
|
* affordance, and every piece of data is fetched on demand.
|
|
14619
14676
|
*/
|
|
14677
|
+
/** Read the optional workspace target carried from the pairing QR flow. */
|
|
14678
|
+
function mobileWorkspaceTarget(search) {
|
|
14679
|
+
const value = new URLSearchParams(search).get("workspace");
|
|
14680
|
+
return value === null || value === "" ? void 0 : value;
|
|
14681
|
+
}
|
|
14620
14682
|
/** Map a list row to the surface model; the title comes from projections when present. */
|
|
14621
14683
|
function toSessionView(item) {
|
|
14622
14684
|
const titleValue = item.projections?.values?.title;
|
|
@@ -14647,6 +14709,7 @@ function formatTime(epochMs) {
|
|
|
14647
14709
|
*/
|
|
14648
14710
|
function App() {
|
|
14649
14711
|
const [route, setRoute] = (0, import_react.useState)({ kind: "workspaces" });
|
|
14712
|
+
const [initialWorkspaceId, setInitialWorkspaceId] = (0, import_react.useState)(() => mobileWorkspaceTarget(window.location.search));
|
|
14650
14713
|
const muxRef = (0, import_react.useRef)(void 0);
|
|
14651
14714
|
(0, import_react.useEffect)(() => {
|
|
14652
14715
|
const mux = new MuxClient();
|
|
@@ -14676,14 +14739,24 @@ function App() {
|
|
|
14676
14739
|
workspace
|
|
14677
14740
|
});
|
|
14678
14741
|
}, []);
|
|
14742
|
+
const openWorkspace = (0, import_react.useCallback)((workspace) => {
|
|
14743
|
+
if (initialWorkspaceId !== void 0) {
|
|
14744
|
+
const url = new URL(window.location.href);
|
|
14745
|
+
url.searchParams.delete("workspace");
|
|
14746
|
+
window.history.replaceState(null, "", `${url.pathname}${url.search}${url.hash}`);
|
|
14747
|
+
setInitialWorkspaceId(void 0);
|
|
14748
|
+
}
|
|
14749
|
+
setRoute({
|
|
14750
|
+
kind: "sessions",
|
|
14751
|
+
workspace
|
|
14752
|
+
});
|
|
14753
|
+
}, [initialWorkspaceId]);
|
|
14679
14754
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
14680
14755
|
className: "mobile",
|
|
14681
|
-
children: route.kind === "workspaces" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceView, {
|
|
14682
|
-
|
|
14683
|
-
|
|
14684
|
-
|
|
14685
|
-
});
|
|
14686
|
-
} }) : route.kind === "sessions" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SessionListView, {
|
|
14756
|
+
children: route.kind === "workspaces" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceView, {
|
|
14757
|
+
initialWorkspaceId,
|
|
14758
|
+
onPick: openWorkspace
|
|
14759
|
+
}) : route.kind === "sessions" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SessionListView, {
|
|
14687
14760
|
workspace: route.workspace,
|
|
14688
14761
|
onBack: back,
|
|
14689
14762
|
onPick: (session) => {
|
|
@@ -14714,8 +14787,8 @@ function staleHostHint(message) {
|
|
|
14714
14787
|
return /^HTTP 403/.test(message) ? "宿主端插件可能仍在运行旧版本:请重启 dsh web 后再试。" : void 0;
|
|
14715
14788
|
}
|
|
14716
14789
|
/** Fetch one history page (tail by default) — thin wrapper so views share the call shape. */
|
|
14717
|
-
function loadHistory(sessionId, beforeSeq) {
|
|
14718
|
-
return history(sessionId, beforeSeq);
|
|
14790
|
+
function loadHistory(sessionId, beforeSeq, signal) {
|
|
14791
|
+
return history(sessionId, beforeSeq, void 0, signal);
|
|
14719
14792
|
}
|
|
14720
14793
|
//#endregion
|
|
14721
14794
|
//#region src/mobile/mobile-styles.ts
|
|
@@ -15486,6 +15559,9 @@ body {
|
|
|
15486
15559
|
min-width: 0;
|
|
15487
15560
|
min-height: 40px;
|
|
15488
15561
|
max-height: 120px;
|
|
15562
|
+
/* Grow with the content (still bounded by min/max-height); browsers
|
|
15563
|
+
without field-sizing support ignore this line and keep the old size. */
|
|
15564
|
+
field-sizing: content;
|
|
15489
15565
|
padding: 9px 12px;
|
|
15490
15566
|
border: 1px solid var(--m-border);
|
|
15491
15567
|
border-radius: 10px;
|