dsh-monkeyai-sessions 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.md +1 -1
- package/README.zh.md +1 -1
- package/client.js +108 -21
- package/lib/client-runtime.cjs +108 -21
- package/package.json +1 -1
- package/src/client/index.js +86 -17
- package/src/host/monkeyai-remote.js +27 -3
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ From a checkout:
|
|
|
61
61
|
npm install
|
|
62
62
|
npm run build
|
|
63
63
|
npm pack
|
|
64
|
-
dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.
|
|
64
|
+
dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.14.tgz
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
Verified against a real DSH `0.2.2-rc.1` web profile and a live MonkeyAI runtime: authorization, full history loading, prompt delivery, model switching, and turn cancellation all work end to end.
|
package/README.zh.md
CHANGED
|
@@ -28,7 +28,7 @@ https://artenx.github.io/dsh-monkeyai-sessions/index.json
|
|
|
28
28
|
npm install
|
|
29
29
|
npm run build
|
|
30
30
|
npm pack
|
|
31
|
-
dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.
|
|
31
|
+
dsh plugin --profile web add ./dsh-monkeyai-sessions-0.1.14.tgz
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
已在真实的 DSH `0.2.2-rc.1` web profile 与在线 MonkeyAI runtime 上端到端验证:授权、完整历史加载、指令下发、模型切换、回合取消均正常工作。
|
package/client.js
CHANGED
|
@@ -553,7 +553,7 @@ window.__ModuleLoader__.load({
|
|
|
553
553
|
toolbarCard: { display: "grid", gap: 10, padding: "13px 15px", border: `1px solid ${t.border}`, borderRadius: 12, background: t.surface, backgroundImage: "linear-gradient(90deg, #3568e8 0%, #8baaf4 46%, #e7edfc 100%)", backgroundSize: "100% 3px", backgroundPosition: "0 0", backgroundRepeat: "no-repeat", boxShadow: "0 2px 8px rgba(16,24,40,0.035)" },
|
|
554
554
|
workspace: { display: "grid", gridTemplateColumns: "minmax(248px, 292px) minmax(0, 1fr)", gap: 12, minWidth: 0, alignItems: "stretch" },
|
|
555
555
|
sessionSidebar: { display: "grid", gridTemplateRows: "auto minmax(0, 1fr) auto", gap: 10, minWidth: 0, padding: 12, border: `1px solid ${t.border}`, borderRadius: 12, background: t.surface, boxShadow: "0 2px 8px rgba(16,24,40,0.035)" },
|
|
556
|
-
sessionList: { display: "grid",
|
|
556
|
+
sessionList: { display: "grid", alignContent: "start", gap: 6, minHeight: 0, overflowY: "auto", padding: "1px 3px 2px 1px" },
|
|
557
557
|
thread: { display: "grid", gridTemplateRows: "auto minmax(0, 1fr) auto", gap: 10, minWidth: 0, height: "min(70vh, 720px)", minHeight: 440, padding: 12 },
|
|
558
558
|
emptyThread: { display: "grid", placeContent: "center", justifyItems: "center", gap: 9, minWidth: 0, padding: 28, border: `1px dashed ${t.border2}`, borderRadius: 12, background: "linear-gradient(145deg, #ffffff 0%, #f8faff 100%)", textAlign: "center" },
|
|
559
559
|
row: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" },
|
|
@@ -568,7 +568,6 @@ window.__ModuleLoader__.load({
|
|
|
568
568
|
statusBadge: { display: "inline-flex", alignItems: "center", gap: 6, padding: "3px 10px", borderRadius: 999, fontSize: 11.5, fontWeight: 600, background: t.surfaceAlt, border: `1px solid ${t.border}`, color: t.text2, whiteSpace: "nowrap", maxWidth: 340, overflow: "hidden", textOverflow: "ellipsis" },
|
|
569
569
|
dot: { width: 7, height: 7, borderRadius: "50%", flexShrink: 0 },
|
|
570
570
|
error: { display: "flex", gap: 8, alignItems: "flex-start", color: t.danger, margin: 0, fontSize: 12.5, background: t.dangerBg, border: `1px solid ${t.dangerBorder}`, borderRadius: t.radius, padding: "8px 10px", whiteSpace: "pre-wrap", overflowWrap: "anywhere" },
|
|
571
|
-
sessions: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(230px, 1fr))", gap: 8 },
|
|
572
571
|
session: { display: "grid", gap: 4, textAlign: "left", padding: "10px 12px", borderRadius: t.radius, border: `1px solid ${t.border}`, background: t.surface, color: t.text, cursor: "pointer", transition: "border-color .15s, background .15s" },
|
|
573
572
|
selected: { border: `1px solid ${t.accent}`, background: t.accentSoft, boxShadow: `0 0 0 1px ${t.accent} inset` },
|
|
574
573
|
sessionTitle: { fontSize: 13, fontWeight: 650, lineHeight: 1.35, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
@@ -587,7 +586,18 @@ window.__ModuleLoader__.load({
|
|
|
587
586
|
composer: { display: "flex", gap: 8, alignItems: "flex-end" },
|
|
588
587
|
textarea: { width: "100%", minHeight: 56, maxHeight: 200, boxSizing: "border-box", padding: "8px 11px", borderRadius: t.radius, border: `1px solid ${t.border2}`, background: t.surface, color: t.text, resize: "vertical", outline: "none", fontSize: 13, lineHeight: 1.5, transition: "border-color .15s, box-shadow .15s" },
|
|
589
588
|
details: { fontSize: 12, color: t.text2 },
|
|
590
|
-
summary: { cursor: "pointer", userSelect: "none", fontSize: 12, color: t.text2 }
|
|
589
|
+
summary: { cursor: "pointer", userSelect: "none", fontSize: 12, color: t.text2 },
|
|
590
|
+
modelPicker: { position: "relative", flex: "0 1 200px", minWidth: 132, maxWidth: "100%" },
|
|
591
|
+
modelPickerSummary: { display: "flex", alignItems: "center", gap: 6, width: "100%", boxSizing: "border-box", padding: "7px 10px", borderRadius: t.radius, border: `1px solid ${t.border2}`, background: t.surface, color: t.text, outline: "none", fontSize: 13, cursor: "pointer", listStyle: "none", userSelect: "none" },
|
|
592
|
+
modelPickerCaret: { flexShrink: 0, color: t.text3, fontSize: 9 },
|
|
593
|
+
modelMenu: { position: "absolute", right: 0, top: "calc(100% + 6px)", zIndex: 40, width: "max(100%, 264px)", maxWidth: "94vw", boxSizing: "border-box", display: "grid", gap: 6, padding: 8, borderRadius: 10, border: `1px solid ${t.border}`, background: t.surface, boxShadow: "0 10px 30px rgba(16,24,40,0.16)" },
|
|
594
|
+
modelMenuList: { display: "grid", gap: 2, maxHeight: "52vh", overflowY: "auto", overscrollBehavior: "contain" },
|
|
595
|
+
modelItem: { display: "flex", alignItems: "baseline", gap: 8, width: "100%", boxSizing: "border-box", padding: "7px 9px", borderRadius: 7, border: "1px solid transparent", background: "transparent", color: t.text, cursor: "pointer", fontSize: 12.5, textAlign: "left", transition: "background .12s" },
|
|
596
|
+
modelItemActive: { background: t.accentSoft, borderColor: t.border, fontWeight: 650 },
|
|
597
|
+
modelItemName: { flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", textAlign: "left" },
|
|
598
|
+
modelItemMeta: { flexShrink: 0, fontSize: 11, color: t.text3 },
|
|
599
|
+
modelSearch: { width: "100%", boxSizing: "border-box", padding: "6px 9px", borderRadius: t.radius, border: `1px solid ${t.border2}`, background: t.surface, color: t.text, outline: "none", fontSize: 12.5 },
|
|
600
|
+
modelEmpty: { fontSize: 12, color: t.text3, textAlign: "center", padding: "10px 0" }
|
|
591
601
|
};
|
|
592
602
|
var PANEL_CSS = `
|
|
593
603
|
.mai-panel input:focus, .mai-panel textarea:focus, .mai-panel select:focus { border-color: #3568e8 !important; box-shadow: 0 0 0 2px rgba(53,104,232,0.14) !important; }
|
|
@@ -604,6 +614,10 @@ window.__ModuleLoader__.load({
|
|
|
604
614
|
.mai-panel .mai-card-hover { transition: border-color .15s, background .15s; }
|
|
605
615
|
.mai-panel .mai-card-hover:not(:disabled):hover { border-color: #b9cdf3 !important; background: #f7faff !important; }
|
|
606
616
|
.mai-panel .mai-toolbar { position: relative; }
|
|
617
|
+
.mai-panel .mai-model-picker > summary::-webkit-details-marker { display: none; }
|
|
618
|
+
.mai-panel .mai-model-picker > summary::before { content: none; }
|
|
619
|
+
.mai-panel .mai-model-picker-menu { max-height: 52vh; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
|
|
620
|
+
.mai-panel .mai-model-picker[open] .mai-model-picker-caret { transform: rotate(180deg); }
|
|
607
621
|
.mai-panel .mai-workspace { display: grid; grid-template-columns: minmax(248px, 292px) minmax(0, 1fr); gap: 12px; min-width: 0; align-items: stretch; }
|
|
608
622
|
.mai-panel .mai-session-sidebar { min-height: min(70vh, 720px); }
|
|
609
623
|
.mai-panel .mai-session-list { scrollbar-gutter: stable; }
|
|
@@ -625,6 +639,9 @@ window.__ModuleLoader__.load({
|
|
|
625
639
|
.mai-panel .mai-page-title { font-size: 16px !important; }
|
|
626
640
|
.mai-panel .mai-toolbar-controls { align-items: stretch; }
|
|
627
641
|
.mai-panel .mai-toolbar-controls select { flex-basis: 100% !important; }
|
|
642
|
+
.mai-panel .mai-model-picker { flex: 1 1 100% !important; min-width: 0 !important; }
|
|
643
|
+
.mai-panel .mai-model-menu { position: fixed !important; inset: 50% 12px auto !important; transform: translateY(-50%); width: auto !important; max-width: none !important; max-height: 70vh !important; }
|
|
644
|
+
.mai-panel .mai-model-picker-menu { max-height: calc(70vh - 80px) !important; }
|
|
628
645
|
.mai-panel .mai-session-list { grid-template-columns: 1fr !important; max-height: 210px; }
|
|
629
646
|
.mai-panel .mai-thread { height: 68vh; min-height: 360px; padding: 10px; }
|
|
630
647
|
.mai-panel .mai-thread-header { gap: 6px !important; }
|
|
@@ -658,7 +675,7 @@ window.__ModuleLoader__.load({
|
|
|
658
675
|
});
|
|
659
676
|
}
|
|
660
677
|
function isRemoteStreamCarrierError(error) {
|
|
661
|
-
return /api gateway: remote stream websocket (?:failed|closed)/i.test(errorText(error));
|
|
678
|
+
return /api gateway: remote stream websocket (?:failed|closed)/i.test(errorText(error)) || /remote stream socket closed/i.test(errorText(error));
|
|
662
679
|
}
|
|
663
680
|
function renderMarkdown(text) {
|
|
664
681
|
if (typeof text !== "string" || text === "") return null;
|
|
@@ -877,6 +894,7 @@ window.__ModuleLoader__.load({
|
|
|
877
894
|
const [sessionStatus, setSessionStatus] = useState("");
|
|
878
895
|
const [signInEmail, setSignInEmail] = useState("");
|
|
879
896
|
const [signInPassword, setSignInPassword] = useState("");
|
|
897
|
+
const [modelFilter, setModelFilter] = useState("");
|
|
880
898
|
const streamRef = useRef(null);
|
|
881
899
|
const historyLoadedRef = useRef(false);
|
|
882
900
|
const lastDraftRef = useRef("");
|
|
@@ -884,6 +902,16 @@ window.__ModuleLoader__.load({
|
|
|
884
902
|
const eventsRef = useRef(null);
|
|
885
903
|
const prevScrollHeightRef = useRef(null);
|
|
886
904
|
const streamGenerationRef = useRef(0);
|
|
905
|
+
const modelPickerRef = useRef(null);
|
|
906
|
+
useEffect(() => {
|
|
907
|
+
if (typeof document === "undefined" || typeof document.addEventListener !== "function") return void 0;
|
|
908
|
+
const onDocClick = (event) => {
|
|
909
|
+
const picker = modelPickerRef.current;
|
|
910
|
+
if (picker && picker.open && picker.contains && !picker.contains(event.target)) picker.removeAttribute("open");
|
|
911
|
+
};
|
|
912
|
+
document.addEventListener("click", onDocClick);
|
|
913
|
+
return () => document.removeEventListener("click", onDocClick);
|
|
914
|
+
}, []);
|
|
887
915
|
const title = useMemo(() => {
|
|
888
916
|
const runtime = runtimeSessions.find((item) => item.id === sessionId);
|
|
889
917
|
const summary = summarySessions.find((item) => item.id === sessionId);
|
|
@@ -1130,6 +1158,7 @@ window.__ModuleLoader__.load({
|
|
|
1130
1158
|
setError("");
|
|
1131
1159
|
setAnswerText("");
|
|
1132
1160
|
setCurrentModel("");
|
|
1161
|
+
setModelFilter("");
|
|
1133
1162
|
setVisibleCount(RECENT_LIMIT);
|
|
1134
1163
|
setHistoryLoading(true);
|
|
1135
1164
|
setStatus("\u6B63\u5728\u8FDE\u63A5\u8FDC\u7A0B\u4F1A\u8BDD");
|
|
@@ -1153,7 +1182,7 @@ window.__ModuleLoader__.load({
|
|
|
1153
1182
|
setStatus("\u5386\u53F2\u5DF2\u52A0\u8F7D");
|
|
1154
1183
|
setHistoryLoading(false);
|
|
1155
1184
|
setSessionStatus((current) => current || "idle");
|
|
1156
|
-
if (frame.model) setCurrentModel(frame.model);
|
|
1185
|
+
if (frame.data?.model) setCurrentModel(frame.data.model);
|
|
1157
1186
|
}
|
|
1158
1187
|
if (frame.type === "history_incomplete") {
|
|
1159
1188
|
setStatus("\u5386\u53F2\u4E0D\u5B8C\u6574\uFF1A" + (frame.message || "\u56DE\u653E\u88AB\u4E2D\u65AD"));
|
|
@@ -1177,6 +1206,9 @@ window.__ModuleLoader__.load({
|
|
|
1177
1206
|
if (frame.type === "request") {
|
|
1178
1207
|
setPendingRequests((current) => [...current.filter((item) => item.requestId !== frame.requestId), frame]);
|
|
1179
1208
|
}
|
|
1209
|
+
if (frame.type === "notification" && frame.method === "_monkeyai/session/model") {
|
|
1210
|
+
if (typeof frame.data?.model === "string" && frame.data.model !== "") setCurrentModel(frame.data.model);
|
|
1211
|
+
}
|
|
1180
1212
|
if (frame.type === "notification" && frame.method === "_monkeyai/session/state") {
|
|
1181
1213
|
const status2 = frame.data?.state?.status;
|
|
1182
1214
|
if (typeof status2 === "string" && SESSION_STATUS_LABEL[status2]) {
|
|
@@ -1741,7 +1773,7 @@ window.__ModuleLoader__.load({
|
|
|
1741
1773
|
h("h2", { style: style.sectionTitle }, "\u4F1A\u8BDD\u5217\u8868"),
|
|
1742
1774
|
h("span", { style: style.chip }, String(sessionOptions.length))
|
|
1743
1775
|
),
|
|
1744
|
-
sessionOptions.length > 0 ? h("div", { style: style.
|
|
1776
|
+
sessionOptions.length > 0 ? h("div", { style: style.sessionList, className: "mai-session-list" }, sessionOptions.map((session) => {
|
|
1745
1777
|
const flag = session.stale ? "\u5DF2\u8FC7\u671F" : session.deviceStatus && session.deviceStatus !== "online" ? session.deviceStatus === "offline" ? "\u79BB\u7EBF" : session.deviceStatus : "";
|
|
1746
1778
|
return h(
|
|
1747
1779
|
"button",
|
|
@@ -1809,21 +1841,76 @@ window.__ModuleLoader__.load({
|
|
|
1809
1841
|
sessionStatus === "running" ? h("span", { className: "mai-pulse", style: { ...style.dot, background: t.accent } }) : null,
|
|
1810
1842
|
SESSION_STATUS_LABEL[sessionStatus]
|
|
1811
1843
|
) : null,
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1844
|
+
(() => {
|
|
1845
|
+
const modelPickerDisabled = !hasControl || switchingModel || models.length === 0;
|
|
1846
|
+
const currentModelInfo = models.find((m) => m.id === currentModel);
|
|
1847
|
+
const currentModelUnknown = Boolean(currentModel && !currentModelInfo);
|
|
1848
|
+
const needle = modelFilter.trim().toLowerCase();
|
|
1849
|
+
const filteredModels = needle === "" ? models : models.filter((m) => `${m.displayName} ${m.model ?? ""} ${m.id}`.toLowerCase().includes(needle));
|
|
1850
|
+
return h(
|
|
1851
|
+
"details",
|
|
1852
|
+
{
|
|
1853
|
+
className: "mai-model-picker",
|
|
1854
|
+
style: { ...style.modelPicker, ...modelPickerDisabled ? { opacity: 0.5, pointerEvents: "none" } : {} },
|
|
1855
|
+
ref: modelPickerRef
|
|
1856
|
+
},
|
|
1857
|
+
h(
|
|
1858
|
+
"summary",
|
|
1859
|
+
{ style: style.modelPickerSummary, title: "\u5207\u6362\u8BE5\u4F1A\u8BDD\u7684\u6A21\u578B" },
|
|
1860
|
+
h("span", { className: "mai-model-picker-caret", style: { ...style.modelPickerCaret, flexShrink: 0 } }, "\u25BE"),
|
|
1861
|
+
h(
|
|
1862
|
+
"span",
|
|
1863
|
+
{ style: { flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", textAlign: "left" } },
|
|
1864
|
+
currentModelInfo ? `${currentModelInfo.displayName} (${Math.round(currentModelInfo.contextWindowTokens / 1e3)}k)` : currentModel ? currentModel.slice(0, 24) : "\u4F1A\u8BDD\u6A21\u578B"
|
|
1865
|
+
)
|
|
1866
|
+
),
|
|
1867
|
+
h(
|
|
1868
|
+
"div",
|
|
1869
|
+
{ className: "mai-model-menu", style: style.modelMenu },
|
|
1870
|
+
models.length > 6 ? h("input", {
|
|
1871
|
+
type: "search",
|
|
1872
|
+
placeholder: "\u641C\u7D22\u6A21\u578B\u2026",
|
|
1873
|
+
"aria-label": "\u641C\u7D22\u6A21\u578B",
|
|
1874
|
+
value: modelFilter,
|
|
1875
|
+
style: style.modelSearch,
|
|
1876
|
+
onChange: (event) => setModelFilter(event.target.value)
|
|
1877
|
+
}) : null,
|
|
1878
|
+
h(
|
|
1879
|
+
"div",
|
|
1880
|
+
{ className: "mai-model-picker-menu", style: style.modelMenuList },
|
|
1881
|
+
currentModelUnknown && (needle === "" || currentModel.toLowerCase().includes(needle)) ? h(
|
|
1882
|
+
"button",
|
|
1883
|
+
{
|
|
1884
|
+
type: "button",
|
|
1885
|
+
disabled: true,
|
|
1886
|
+
"aria-pressed": true,
|
|
1887
|
+
style: { ...style.modelItem, ...style.modelItemActive }
|
|
1888
|
+
},
|
|
1889
|
+
h("span", { style: style.modelItemName }, currentModel.slice(0, 24)),
|
|
1890
|
+
h("span", { style: style.modelItemMeta }, "\u5F53\u524D\u6A21\u578B")
|
|
1891
|
+
) : null,
|
|
1892
|
+
filteredModels.length === 0 ? h("div", { style: style.modelEmpty }, "\u6CA1\u6709\u5339\u914D\u7684\u6A21\u578B") : null,
|
|
1893
|
+
filteredModels.map((m) => h(
|
|
1894
|
+
"button",
|
|
1895
|
+
{
|
|
1896
|
+
key: m.id,
|
|
1897
|
+
type: "button",
|
|
1898
|
+
"aria-pressed": m.id === currentModel,
|
|
1899
|
+
style: { ...style.modelItem, ...m.id === currentModel ? style.modelItemActive : {} },
|
|
1900
|
+
disabled: !hasControl || switchingModel,
|
|
1901
|
+
title: m.model && m.model !== m.displayName ? m.model : void 0,
|
|
1902
|
+
onClick: () => {
|
|
1903
|
+
if (m.id !== currentModel) switchSessionModel(m.id);
|
|
1904
|
+
modelPickerRef.current?.removeAttribute("open");
|
|
1905
|
+
}
|
|
1906
|
+
},
|
|
1907
|
+
h("span", { style: style.modelItemName }, m.displayName),
|
|
1908
|
+
h("span", { style: style.modelItemMeta }, `${Math.round(m.contextWindowTokens / 1e3)}k`)
|
|
1909
|
+
))
|
|
1910
|
+
)
|
|
1911
|
+
)
|
|
1912
|
+
);
|
|
1913
|
+
})()
|
|
1827
1914
|
),
|
|
1828
1915
|
h(
|
|
1829
1916
|
"div",
|
package/lib/client-runtime.cjs
CHANGED
|
@@ -548,7 +548,7 @@ var style = {
|
|
|
548
548
|
toolbarCard: { display: "grid", gap: 10, padding: "13px 15px", border: `1px solid ${t.border}`, borderRadius: 12, background: t.surface, backgroundImage: "linear-gradient(90deg, #3568e8 0%, #8baaf4 46%, #e7edfc 100%)", backgroundSize: "100% 3px", backgroundPosition: "0 0", backgroundRepeat: "no-repeat", boxShadow: "0 2px 8px rgba(16,24,40,0.035)" },
|
|
549
549
|
workspace: { display: "grid", gridTemplateColumns: "minmax(248px, 292px) minmax(0, 1fr)", gap: 12, minWidth: 0, alignItems: "stretch" },
|
|
550
550
|
sessionSidebar: { display: "grid", gridTemplateRows: "auto minmax(0, 1fr) auto", gap: 10, minWidth: 0, padding: 12, border: `1px solid ${t.border}`, borderRadius: 12, background: t.surface, boxShadow: "0 2px 8px rgba(16,24,40,0.035)" },
|
|
551
|
-
sessionList: { display: "grid",
|
|
551
|
+
sessionList: { display: "grid", alignContent: "start", gap: 6, minHeight: 0, overflowY: "auto", padding: "1px 3px 2px 1px" },
|
|
552
552
|
thread: { display: "grid", gridTemplateRows: "auto minmax(0, 1fr) auto", gap: 10, minWidth: 0, height: "min(70vh, 720px)", minHeight: 440, padding: 12 },
|
|
553
553
|
emptyThread: { display: "grid", placeContent: "center", justifyItems: "center", gap: 9, minWidth: 0, padding: 28, border: `1px dashed ${t.border2}`, borderRadius: 12, background: "linear-gradient(145deg, #ffffff 0%, #f8faff 100%)", textAlign: "center" },
|
|
554
554
|
row: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" },
|
|
@@ -563,7 +563,6 @@ var style = {
|
|
|
563
563
|
statusBadge: { display: "inline-flex", alignItems: "center", gap: 6, padding: "3px 10px", borderRadius: 999, fontSize: 11.5, fontWeight: 600, background: t.surfaceAlt, border: `1px solid ${t.border}`, color: t.text2, whiteSpace: "nowrap", maxWidth: 340, overflow: "hidden", textOverflow: "ellipsis" },
|
|
564
564
|
dot: { width: 7, height: 7, borderRadius: "50%", flexShrink: 0 },
|
|
565
565
|
error: { display: "flex", gap: 8, alignItems: "flex-start", color: t.danger, margin: 0, fontSize: 12.5, background: t.dangerBg, border: `1px solid ${t.dangerBorder}`, borderRadius: t.radius, padding: "8px 10px", whiteSpace: "pre-wrap", overflowWrap: "anywhere" },
|
|
566
|
-
sessions: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(230px, 1fr))", gap: 8 },
|
|
567
566
|
session: { display: "grid", gap: 4, textAlign: "left", padding: "10px 12px", borderRadius: t.radius, border: `1px solid ${t.border}`, background: t.surface, color: t.text, cursor: "pointer", transition: "border-color .15s, background .15s" },
|
|
568
567
|
selected: { border: `1px solid ${t.accent}`, background: t.accentSoft, boxShadow: `0 0 0 1px ${t.accent} inset` },
|
|
569
568
|
sessionTitle: { fontSize: 13, fontWeight: 650, lineHeight: 1.35, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
@@ -582,7 +581,18 @@ var style = {
|
|
|
582
581
|
composer: { display: "flex", gap: 8, alignItems: "flex-end" },
|
|
583
582
|
textarea: { width: "100%", minHeight: 56, maxHeight: 200, boxSizing: "border-box", padding: "8px 11px", borderRadius: t.radius, border: `1px solid ${t.border2}`, background: t.surface, color: t.text, resize: "vertical", outline: "none", fontSize: 13, lineHeight: 1.5, transition: "border-color .15s, box-shadow .15s" },
|
|
584
583
|
details: { fontSize: 12, color: t.text2 },
|
|
585
|
-
summary: { cursor: "pointer", userSelect: "none", fontSize: 12, color: t.text2 }
|
|
584
|
+
summary: { cursor: "pointer", userSelect: "none", fontSize: 12, color: t.text2 },
|
|
585
|
+
modelPicker: { position: "relative", flex: "0 1 200px", minWidth: 132, maxWidth: "100%" },
|
|
586
|
+
modelPickerSummary: { display: "flex", alignItems: "center", gap: 6, width: "100%", boxSizing: "border-box", padding: "7px 10px", borderRadius: t.radius, border: `1px solid ${t.border2}`, background: t.surface, color: t.text, outline: "none", fontSize: 13, cursor: "pointer", listStyle: "none", userSelect: "none" },
|
|
587
|
+
modelPickerCaret: { flexShrink: 0, color: t.text3, fontSize: 9 },
|
|
588
|
+
modelMenu: { position: "absolute", right: 0, top: "calc(100% + 6px)", zIndex: 40, width: "max(100%, 264px)", maxWidth: "94vw", boxSizing: "border-box", display: "grid", gap: 6, padding: 8, borderRadius: 10, border: `1px solid ${t.border}`, background: t.surface, boxShadow: "0 10px 30px rgba(16,24,40,0.16)" },
|
|
589
|
+
modelMenuList: { display: "grid", gap: 2, maxHeight: "52vh", overflowY: "auto", overscrollBehavior: "contain" },
|
|
590
|
+
modelItem: { display: "flex", alignItems: "baseline", gap: 8, width: "100%", boxSizing: "border-box", padding: "7px 9px", borderRadius: 7, border: "1px solid transparent", background: "transparent", color: t.text, cursor: "pointer", fontSize: 12.5, textAlign: "left", transition: "background .12s" },
|
|
591
|
+
modelItemActive: { background: t.accentSoft, borderColor: t.border, fontWeight: 650 },
|
|
592
|
+
modelItemName: { flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", textAlign: "left" },
|
|
593
|
+
modelItemMeta: { flexShrink: 0, fontSize: 11, color: t.text3 },
|
|
594
|
+
modelSearch: { width: "100%", boxSizing: "border-box", padding: "6px 9px", borderRadius: t.radius, border: `1px solid ${t.border2}`, background: t.surface, color: t.text, outline: "none", fontSize: 12.5 },
|
|
595
|
+
modelEmpty: { fontSize: 12, color: t.text3, textAlign: "center", padding: "10px 0" }
|
|
586
596
|
};
|
|
587
597
|
var PANEL_CSS = `
|
|
588
598
|
.mai-panel input:focus, .mai-panel textarea:focus, .mai-panel select:focus { border-color: #3568e8 !important; box-shadow: 0 0 0 2px rgba(53,104,232,0.14) !important; }
|
|
@@ -599,6 +609,10 @@ var PANEL_CSS = `
|
|
|
599
609
|
.mai-panel .mai-card-hover { transition: border-color .15s, background .15s; }
|
|
600
610
|
.mai-panel .mai-card-hover:not(:disabled):hover { border-color: #b9cdf3 !important; background: #f7faff !important; }
|
|
601
611
|
.mai-panel .mai-toolbar { position: relative; }
|
|
612
|
+
.mai-panel .mai-model-picker > summary::-webkit-details-marker { display: none; }
|
|
613
|
+
.mai-panel .mai-model-picker > summary::before { content: none; }
|
|
614
|
+
.mai-panel .mai-model-picker-menu { max-height: 52vh; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
|
|
615
|
+
.mai-panel .mai-model-picker[open] .mai-model-picker-caret { transform: rotate(180deg); }
|
|
602
616
|
.mai-panel .mai-workspace { display: grid; grid-template-columns: minmax(248px, 292px) minmax(0, 1fr); gap: 12px; min-width: 0; align-items: stretch; }
|
|
603
617
|
.mai-panel .mai-session-sidebar { min-height: min(70vh, 720px); }
|
|
604
618
|
.mai-panel .mai-session-list { scrollbar-gutter: stable; }
|
|
@@ -620,6 +634,9 @@ var PANEL_CSS = `
|
|
|
620
634
|
.mai-panel .mai-page-title { font-size: 16px !important; }
|
|
621
635
|
.mai-panel .mai-toolbar-controls { align-items: stretch; }
|
|
622
636
|
.mai-panel .mai-toolbar-controls select { flex-basis: 100% !important; }
|
|
637
|
+
.mai-panel .mai-model-picker { flex: 1 1 100% !important; min-width: 0 !important; }
|
|
638
|
+
.mai-panel .mai-model-menu { position: fixed !important; inset: 50% 12px auto !important; transform: translateY(-50%); width: auto !important; max-width: none !important; max-height: 70vh !important; }
|
|
639
|
+
.mai-panel .mai-model-picker-menu { max-height: calc(70vh - 80px) !important; }
|
|
623
640
|
.mai-panel .mai-session-list { grid-template-columns: 1fr !important; max-height: 210px; }
|
|
624
641
|
.mai-panel .mai-thread { height: 68vh; min-height: 360px; padding: 10px; }
|
|
625
642
|
.mai-panel .mai-thread-header { gap: 6px !important; }
|
|
@@ -653,7 +670,7 @@ function withTimeout(promise, timeoutMs, label) {
|
|
|
653
670
|
});
|
|
654
671
|
}
|
|
655
672
|
function isRemoteStreamCarrierError(error) {
|
|
656
|
-
return /api gateway: remote stream websocket (?:failed|closed)/i.test(errorText(error));
|
|
673
|
+
return /api gateway: remote stream websocket (?:failed|closed)/i.test(errorText(error)) || /remote stream socket closed/i.test(errorText(error));
|
|
657
674
|
}
|
|
658
675
|
function renderMarkdown(text) {
|
|
659
676
|
if (typeof text !== "string" || text === "") return null;
|
|
@@ -872,6 +889,7 @@ function RemoteSessionPage({ api }) {
|
|
|
872
889
|
const [sessionStatus, setSessionStatus] = useState("");
|
|
873
890
|
const [signInEmail, setSignInEmail] = useState("");
|
|
874
891
|
const [signInPassword, setSignInPassword] = useState("");
|
|
892
|
+
const [modelFilter, setModelFilter] = useState("");
|
|
875
893
|
const streamRef = useRef(null);
|
|
876
894
|
const historyLoadedRef = useRef(false);
|
|
877
895
|
const lastDraftRef = useRef("");
|
|
@@ -879,6 +897,16 @@ function RemoteSessionPage({ api }) {
|
|
|
879
897
|
const eventsRef = useRef(null);
|
|
880
898
|
const prevScrollHeightRef = useRef(null);
|
|
881
899
|
const streamGenerationRef = useRef(0);
|
|
900
|
+
const modelPickerRef = useRef(null);
|
|
901
|
+
useEffect(() => {
|
|
902
|
+
if (typeof document === "undefined" || typeof document.addEventListener !== "function") return void 0;
|
|
903
|
+
const onDocClick = (event) => {
|
|
904
|
+
const picker = modelPickerRef.current;
|
|
905
|
+
if (picker && picker.open && picker.contains && !picker.contains(event.target)) picker.removeAttribute("open");
|
|
906
|
+
};
|
|
907
|
+
document.addEventListener("click", onDocClick);
|
|
908
|
+
return () => document.removeEventListener("click", onDocClick);
|
|
909
|
+
}, []);
|
|
882
910
|
const title = useMemo(() => {
|
|
883
911
|
const runtime = runtimeSessions.find((item) => item.id === sessionId);
|
|
884
912
|
const summary = summarySessions.find((item) => item.id === sessionId);
|
|
@@ -1125,6 +1153,7 @@ function RemoteSessionPage({ api }) {
|
|
|
1125
1153
|
setError("");
|
|
1126
1154
|
setAnswerText("");
|
|
1127
1155
|
setCurrentModel("");
|
|
1156
|
+
setModelFilter("");
|
|
1128
1157
|
setVisibleCount(RECENT_LIMIT);
|
|
1129
1158
|
setHistoryLoading(true);
|
|
1130
1159
|
setStatus("\u6B63\u5728\u8FDE\u63A5\u8FDC\u7A0B\u4F1A\u8BDD");
|
|
@@ -1148,7 +1177,7 @@ function RemoteSessionPage({ api }) {
|
|
|
1148
1177
|
setStatus("\u5386\u53F2\u5DF2\u52A0\u8F7D");
|
|
1149
1178
|
setHistoryLoading(false);
|
|
1150
1179
|
setSessionStatus((current) => current || "idle");
|
|
1151
|
-
if (frame.model) setCurrentModel(frame.model);
|
|
1180
|
+
if (frame.data?.model) setCurrentModel(frame.data.model);
|
|
1152
1181
|
}
|
|
1153
1182
|
if (frame.type === "history_incomplete") {
|
|
1154
1183
|
setStatus("\u5386\u53F2\u4E0D\u5B8C\u6574\uFF1A" + (frame.message || "\u56DE\u653E\u88AB\u4E2D\u65AD"));
|
|
@@ -1172,6 +1201,9 @@ function RemoteSessionPage({ api }) {
|
|
|
1172
1201
|
if (frame.type === "request") {
|
|
1173
1202
|
setPendingRequests((current) => [...current.filter((item) => item.requestId !== frame.requestId), frame]);
|
|
1174
1203
|
}
|
|
1204
|
+
if (frame.type === "notification" && frame.method === "_monkeyai/session/model") {
|
|
1205
|
+
if (typeof frame.data?.model === "string" && frame.data.model !== "") setCurrentModel(frame.data.model);
|
|
1206
|
+
}
|
|
1175
1207
|
if (frame.type === "notification" && frame.method === "_monkeyai/session/state") {
|
|
1176
1208
|
const status2 = frame.data?.state?.status;
|
|
1177
1209
|
if (typeof status2 === "string" && SESSION_STATUS_LABEL[status2]) {
|
|
@@ -1736,7 +1768,7 @@ function RemoteSessionPage({ api }) {
|
|
|
1736
1768
|
h("h2", { style: style.sectionTitle }, "\u4F1A\u8BDD\u5217\u8868"),
|
|
1737
1769
|
h("span", { style: style.chip }, String(sessionOptions.length))
|
|
1738
1770
|
),
|
|
1739
|
-
sessionOptions.length > 0 ? h("div", { style: style.
|
|
1771
|
+
sessionOptions.length > 0 ? h("div", { style: style.sessionList, className: "mai-session-list" }, sessionOptions.map((session) => {
|
|
1740
1772
|
const flag = session.stale ? "\u5DF2\u8FC7\u671F" : session.deviceStatus && session.deviceStatus !== "online" ? session.deviceStatus === "offline" ? "\u79BB\u7EBF" : session.deviceStatus : "";
|
|
1741
1773
|
return h(
|
|
1742
1774
|
"button",
|
|
@@ -1804,21 +1836,76 @@ function RemoteSessionPage({ api }) {
|
|
|
1804
1836
|
sessionStatus === "running" ? h("span", { className: "mai-pulse", style: { ...style.dot, background: t.accent } }) : null,
|
|
1805
1837
|
SESSION_STATUS_LABEL[sessionStatus]
|
|
1806
1838
|
) : null,
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1839
|
+
(() => {
|
|
1840
|
+
const modelPickerDisabled = !hasControl || switchingModel || models.length === 0;
|
|
1841
|
+
const currentModelInfo = models.find((m) => m.id === currentModel);
|
|
1842
|
+
const currentModelUnknown = Boolean(currentModel && !currentModelInfo);
|
|
1843
|
+
const needle = modelFilter.trim().toLowerCase();
|
|
1844
|
+
const filteredModels = needle === "" ? models : models.filter((m) => `${m.displayName} ${m.model ?? ""} ${m.id}`.toLowerCase().includes(needle));
|
|
1845
|
+
return h(
|
|
1846
|
+
"details",
|
|
1847
|
+
{
|
|
1848
|
+
className: "mai-model-picker",
|
|
1849
|
+
style: { ...style.modelPicker, ...modelPickerDisabled ? { opacity: 0.5, pointerEvents: "none" } : {} },
|
|
1850
|
+
ref: modelPickerRef
|
|
1851
|
+
},
|
|
1852
|
+
h(
|
|
1853
|
+
"summary",
|
|
1854
|
+
{ style: style.modelPickerSummary, title: "\u5207\u6362\u8BE5\u4F1A\u8BDD\u7684\u6A21\u578B" },
|
|
1855
|
+
h("span", { className: "mai-model-picker-caret", style: { ...style.modelPickerCaret, flexShrink: 0 } }, "\u25BE"),
|
|
1856
|
+
h(
|
|
1857
|
+
"span",
|
|
1858
|
+
{ style: { flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", textAlign: "left" } },
|
|
1859
|
+
currentModelInfo ? `${currentModelInfo.displayName} (${Math.round(currentModelInfo.contextWindowTokens / 1e3)}k)` : currentModel ? currentModel.slice(0, 24) : "\u4F1A\u8BDD\u6A21\u578B"
|
|
1860
|
+
)
|
|
1861
|
+
),
|
|
1862
|
+
h(
|
|
1863
|
+
"div",
|
|
1864
|
+
{ className: "mai-model-menu", style: style.modelMenu },
|
|
1865
|
+
models.length > 6 ? h("input", {
|
|
1866
|
+
type: "search",
|
|
1867
|
+
placeholder: "\u641C\u7D22\u6A21\u578B\u2026",
|
|
1868
|
+
"aria-label": "\u641C\u7D22\u6A21\u578B",
|
|
1869
|
+
value: modelFilter,
|
|
1870
|
+
style: style.modelSearch,
|
|
1871
|
+
onChange: (event) => setModelFilter(event.target.value)
|
|
1872
|
+
}) : null,
|
|
1873
|
+
h(
|
|
1874
|
+
"div",
|
|
1875
|
+
{ className: "mai-model-picker-menu", style: style.modelMenuList },
|
|
1876
|
+
currentModelUnknown && (needle === "" || currentModel.toLowerCase().includes(needle)) ? h(
|
|
1877
|
+
"button",
|
|
1878
|
+
{
|
|
1879
|
+
type: "button",
|
|
1880
|
+
disabled: true,
|
|
1881
|
+
"aria-pressed": true,
|
|
1882
|
+
style: { ...style.modelItem, ...style.modelItemActive }
|
|
1883
|
+
},
|
|
1884
|
+
h("span", { style: style.modelItemName }, currentModel.slice(0, 24)),
|
|
1885
|
+
h("span", { style: style.modelItemMeta }, "\u5F53\u524D\u6A21\u578B")
|
|
1886
|
+
) : null,
|
|
1887
|
+
filteredModels.length === 0 ? h("div", { style: style.modelEmpty }, "\u6CA1\u6709\u5339\u914D\u7684\u6A21\u578B") : null,
|
|
1888
|
+
filteredModels.map((m) => h(
|
|
1889
|
+
"button",
|
|
1890
|
+
{
|
|
1891
|
+
key: m.id,
|
|
1892
|
+
type: "button",
|
|
1893
|
+
"aria-pressed": m.id === currentModel,
|
|
1894
|
+
style: { ...style.modelItem, ...m.id === currentModel ? style.modelItemActive : {} },
|
|
1895
|
+
disabled: !hasControl || switchingModel,
|
|
1896
|
+
title: m.model && m.model !== m.displayName ? m.model : void 0,
|
|
1897
|
+
onClick: () => {
|
|
1898
|
+
if (m.id !== currentModel) switchSessionModel(m.id);
|
|
1899
|
+
modelPickerRef.current?.removeAttribute("open");
|
|
1900
|
+
}
|
|
1901
|
+
},
|
|
1902
|
+
h("span", { style: style.modelItemName }, m.displayName),
|
|
1903
|
+
h("span", { style: style.modelItemMeta }, `${Math.round(m.contextWindowTokens / 1e3)}k`)
|
|
1904
|
+
))
|
|
1905
|
+
)
|
|
1906
|
+
)
|
|
1907
|
+
);
|
|
1908
|
+
})()
|
|
1822
1909
|
),
|
|
1823
1910
|
h(
|
|
1824
1911
|
"div",
|
package/package.json
CHANGED
package/src/client/index.js
CHANGED
|
@@ -38,7 +38,7 @@ const style = {
|
|
|
38
38
|
toolbarCard: { display: 'grid', gap: 10, padding: '13px 15px', border: `1px solid ${t.border}`, borderRadius: 12, background: t.surface, backgroundImage: 'linear-gradient(90deg, #3568e8 0%, #8baaf4 46%, #e7edfc 100%)', backgroundSize: '100% 3px', backgroundPosition: '0 0', backgroundRepeat: 'no-repeat', boxShadow: '0 2px 8px rgba(16,24,40,0.035)' },
|
|
39
39
|
workspace: { display: 'grid', gridTemplateColumns: 'minmax(248px, 292px) minmax(0, 1fr)', gap: 12, minWidth: 0, alignItems: 'stretch' },
|
|
40
40
|
sessionSidebar: { display: 'grid', gridTemplateRows: 'auto minmax(0, 1fr) auto', gap: 10, minWidth: 0, padding: 12, border: `1px solid ${t.border}`, borderRadius: 12, background: t.surface, boxShadow: '0 2px 8px rgba(16,24,40,0.035)' },
|
|
41
|
-
sessionList: { display: 'grid',
|
|
41
|
+
sessionList: { display: 'grid', alignContent: 'start', gap: 6, minHeight: 0, overflowY: 'auto', padding: '1px 3px 2px 1px' },
|
|
42
42
|
thread: { display: 'grid', gridTemplateRows: 'auto minmax(0, 1fr) auto', gap: 10, minWidth: 0, height: 'min(70vh, 720px)', minHeight: 440, padding: 12 },
|
|
43
43
|
emptyThread: { display: 'grid', placeContent: 'center', justifyItems: 'center', gap: 9, minWidth: 0, padding: 28, border: `1px dashed ${t.border2}`, borderRadius: 12, background: 'linear-gradient(145deg, #ffffff 0%, #f8faff 100%)', textAlign: 'center' },
|
|
44
44
|
row: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' },
|
|
@@ -53,7 +53,6 @@ const style = {
|
|
|
53
53
|
statusBadge: { display: 'inline-flex', alignItems: 'center', gap: 6, padding: '3px 10px', borderRadius: 999, fontSize: 11.5, fontWeight: 600, background: t.surfaceAlt, border: `1px solid ${t.border}`, color: t.text2, whiteSpace: 'nowrap', maxWidth: 340, overflow: 'hidden', textOverflow: 'ellipsis' },
|
|
54
54
|
dot: { width: 7, height: 7, borderRadius: '50%', flexShrink: 0 },
|
|
55
55
|
error: { display: 'flex', gap: 8, alignItems: 'flex-start', color: t.danger, margin: 0, fontSize: 12.5, background: t.dangerBg, border: `1px solid ${t.dangerBorder}`, borderRadius: t.radius, padding: '8px 10px', whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' },
|
|
56
|
-
sessions: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(230px, 1fr))', gap: 8 },
|
|
57
56
|
session: { display: 'grid', gap: 4, textAlign: 'left', padding: '10px 12px', borderRadius: t.radius, border: `1px solid ${t.border}`, background: t.surface, color: t.text, cursor: 'pointer', transition: 'border-color .15s, background .15s' },
|
|
58
57
|
selected: { border: `1px solid ${t.accent}`, background: t.accentSoft, boxShadow: `0 0 0 1px ${t.accent} inset` },
|
|
59
58
|
sessionTitle: { fontSize: 13, fontWeight: 650, lineHeight: 1.35, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
|
|
@@ -73,6 +72,17 @@ const style = {
|
|
|
73
72
|
textarea: { width: '100%', minHeight: 56, maxHeight: 200, boxSizing: 'border-box', padding: '8px 11px', borderRadius: t.radius, border: `1px solid ${t.border2}`, background: t.surface, color: t.text, resize: 'vertical', outline: 'none', fontSize: 13, lineHeight: 1.5, transition: 'border-color .15s, box-shadow .15s' },
|
|
74
73
|
details: { fontSize: 12, color: t.text2 },
|
|
75
74
|
summary: { cursor: 'pointer', userSelect: 'none', fontSize: 12, color: t.text2 },
|
|
75
|
+
modelPicker: { position: 'relative', flex: '0 1 200px', minWidth: 132, maxWidth: '100%' },
|
|
76
|
+
modelPickerSummary: { display: 'flex', alignItems: 'center', gap: 6, width: '100%', boxSizing: 'border-box', padding: '7px 10px', borderRadius: t.radius, border: `1px solid ${t.border2}`, background: t.surface, color: t.text, outline: 'none', fontSize: 13, cursor: 'pointer', listStyle: 'none', userSelect: 'none' },
|
|
77
|
+
modelPickerCaret: { flexShrink: 0, color: t.text3, fontSize: 9 },
|
|
78
|
+
modelMenu: { position: 'absolute', right: 0, top: 'calc(100% + 6px)', zIndex: 40, width: 'max(100%, 264px)', maxWidth: '94vw', boxSizing: 'border-box', display: 'grid', gap: 6, padding: 8, borderRadius: 10, border: `1px solid ${t.border}`, background: t.surface, boxShadow: '0 10px 30px rgba(16,24,40,0.16)' },
|
|
79
|
+
modelMenuList: { display: 'grid', gap: 2, maxHeight: '52vh', overflowY: 'auto', overscrollBehavior: 'contain' },
|
|
80
|
+
modelItem: { display: 'flex', alignItems: 'baseline', gap: 8, width: '100%', boxSizing: 'border-box', padding: '7px 9px', borderRadius: 7, border: '1px solid transparent', background: 'transparent', color: t.text, cursor: 'pointer', fontSize: 12.5, textAlign: 'left', transition: 'background .12s' },
|
|
81
|
+
modelItemActive: { background: t.accentSoft, borderColor: t.border, fontWeight: 650 },
|
|
82
|
+
modelItemName: { flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'left' },
|
|
83
|
+
modelItemMeta: { flexShrink: 0, fontSize: 11, color: t.text3 },
|
|
84
|
+
modelSearch: { width: '100%', boxSizing: 'border-box', padding: '6px 9px', borderRadius: t.radius, border: `1px solid ${t.border2}`, background: t.surface, color: t.text, outline: 'none', fontSize: 12.5 },
|
|
85
|
+
modelEmpty: { fontSize: 12, color: t.text3, textAlign: 'center', padding: '10px 0' },
|
|
76
86
|
}
|
|
77
87
|
|
|
78
88
|
const PANEL_CSS = `
|
|
@@ -90,6 +100,10 @@ const PANEL_CSS = `
|
|
|
90
100
|
.mai-panel .mai-card-hover { transition: border-color .15s, background .15s; }
|
|
91
101
|
.mai-panel .mai-card-hover:not(:disabled):hover { border-color: #b9cdf3 !important; background: #f7faff !important; }
|
|
92
102
|
.mai-panel .mai-toolbar { position: relative; }
|
|
103
|
+
.mai-panel .mai-model-picker > summary::-webkit-details-marker { display: none; }
|
|
104
|
+
.mai-panel .mai-model-picker > summary::before { content: none; }
|
|
105
|
+
.mai-panel .mai-model-picker-menu { max-height: 52vh; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
|
|
106
|
+
.mai-panel .mai-model-picker[open] .mai-model-picker-caret { transform: rotate(180deg); }
|
|
93
107
|
.mai-panel .mai-workspace { display: grid; grid-template-columns: minmax(248px, 292px) minmax(0, 1fr); gap: 12px; min-width: 0; align-items: stretch; }
|
|
94
108
|
.mai-panel .mai-session-sidebar { min-height: min(70vh, 720px); }
|
|
95
109
|
.mai-panel .mai-session-list { scrollbar-gutter: stable; }
|
|
@@ -111,6 +125,9 @@ const PANEL_CSS = `
|
|
|
111
125
|
.mai-panel .mai-page-title { font-size: 16px !important; }
|
|
112
126
|
.mai-panel .mai-toolbar-controls { align-items: stretch; }
|
|
113
127
|
.mai-panel .mai-toolbar-controls select { flex-basis: 100% !important; }
|
|
128
|
+
.mai-panel .mai-model-picker { flex: 1 1 100% !important; min-width: 0 !important; }
|
|
129
|
+
.mai-panel .mai-model-menu { position: fixed !important; inset: 50% 12px auto !important; transform: translateY(-50%); width: auto !important; max-width: none !important; max-height: 70vh !important; }
|
|
130
|
+
.mai-panel .mai-model-picker-menu { max-height: calc(70vh - 80px) !important; }
|
|
114
131
|
.mai-panel .mai-session-list { grid-template-columns: 1fr !important; max-height: 210px; }
|
|
115
132
|
.mai-panel .mai-thread { height: 68vh; min-height: 360px; padding: 10px; }
|
|
116
133
|
.mai-panel .mai-thread-header { gap: 6px !important; }
|
|
@@ -142,6 +159,7 @@ function withTimeout(promise, timeoutMs, label) {
|
|
|
142
159
|
|
|
143
160
|
function isRemoteStreamCarrierError(error) {
|
|
144
161
|
return /api gateway: remote stream websocket (?:failed|closed)/i.test(errorText(error))
|
|
162
|
+
|| /remote stream socket closed/i.test(errorText(error))
|
|
145
163
|
}
|
|
146
164
|
|
|
147
165
|
// Lightweight inline markdown: fenced code blocks, `code`, **bold**,
|
|
@@ -343,6 +361,7 @@ function RemoteSessionPage({ api }) {
|
|
|
343
361
|
const [sessionStatus, setSessionStatus] = useState('')
|
|
344
362
|
const [signInEmail, setSignInEmail] = useState('')
|
|
345
363
|
const [signInPassword, setSignInPassword] = useState('')
|
|
364
|
+
const [modelFilter, setModelFilter] = useState('')
|
|
346
365
|
const streamRef = useRef(null)
|
|
347
366
|
const historyLoadedRef = useRef(false)
|
|
348
367
|
const lastDraftRef = useRef('')
|
|
@@ -350,6 +369,17 @@ function RemoteSessionPage({ api }) {
|
|
|
350
369
|
const eventsRef = useRef(null)
|
|
351
370
|
const prevScrollHeightRef = useRef(null)
|
|
352
371
|
const streamGenerationRef = useRef(0)
|
|
372
|
+
const modelPickerRef = useRef(null)
|
|
373
|
+
|
|
374
|
+
useEffect(() => {
|
|
375
|
+
if (typeof document === 'undefined' || typeof document.addEventListener !== 'function') return undefined
|
|
376
|
+
const onDocClick = event => {
|
|
377
|
+
const picker = modelPickerRef.current
|
|
378
|
+
if (picker && picker.open && picker.contains && !picker.contains(event.target)) picker.removeAttribute('open')
|
|
379
|
+
}
|
|
380
|
+
document.addEventListener('click', onDocClick)
|
|
381
|
+
return () => document.removeEventListener('click', onDocClick)
|
|
382
|
+
}, [])
|
|
353
383
|
const title = useMemo(() => {
|
|
354
384
|
const runtime = runtimeSessions.find(item => item.id === sessionId)
|
|
355
385
|
const summary = summarySessions.find(item => item.id === sessionId)
|
|
@@ -606,6 +636,7 @@ function RemoteSessionPage({ api }) {
|
|
|
606
636
|
setError('')
|
|
607
637
|
setAnswerText('')
|
|
608
638
|
setCurrentModel('')
|
|
639
|
+
setModelFilter('')
|
|
609
640
|
setVisibleCount(RECENT_LIMIT)
|
|
610
641
|
setHistoryLoading(true)
|
|
611
642
|
setStatus('正在连接远程会话')
|
|
@@ -629,7 +660,7 @@ function RemoteSessionPage({ api }) {
|
|
|
629
660
|
setStatus('历史已加载')
|
|
630
661
|
setHistoryLoading(false)
|
|
631
662
|
setSessionStatus(current => current || 'idle')
|
|
632
|
-
if (frame.model) setCurrentModel(frame.model)
|
|
663
|
+
if (frame.data?.model) setCurrentModel(frame.data.model)
|
|
633
664
|
}
|
|
634
665
|
if (frame.type === 'history_incomplete') {
|
|
635
666
|
setStatus('历史不完整:' + (frame.message || '回放被中断'))
|
|
@@ -653,6 +684,9 @@ function RemoteSessionPage({ api }) {
|
|
|
653
684
|
if (frame.type === 'request') {
|
|
654
685
|
setPendingRequests(current => [...current.filter(item => item.requestId !== frame.requestId), frame])
|
|
655
686
|
}
|
|
687
|
+
if (frame.type === 'notification' && frame.method === '_monkeyai/session/model') {
|
|
688
|
+
if (typeof frame.data?.model === 'string' && frame.data.model !== '') setCurrentModel(frame.data.model)
|
|
689
|
+
}
|
|
656
690
|
if (frame.type === 'notification' && frame.method === '_monkeyai/session/state') {
|
|
657
691
|
const status = frame.data?.state?.status
|
|
658
692
|
if (typeof status === 'string' && SESSION_STATUS_LABEL[status]) {
|
|
@@ -1101,7 +1135,7 @@ function RemoteSessionPage({ api }) {
|
|
|
1101
1135
|
h('h2', { style: style.sectionTitle }, '会话列表'),
|
|
1102
1136
|
h('span', { style: style.chip }, String(sessionOptions.length))),
|
|
1103
1137
|
sessionOptions.length > 0
|
|
1104
|
-
? h('div', { style: style.
|
|
1138
|
+
? h('div', { style: style.sessionList, className: 'mai-session-list' }, sessionOptions.map(session => {
|
|
1105
1139
|
const flag = session.stale ? '已过期' : session.deviceStatus && session.deviceStatus !== 'online' ? (session.deviceStatus === 'offline' ? '离线' : session.deviceStatus) : ''
|
|
1106
1140
|
return h('button', {
|
|
1107
1141
|
style: { ...style.session, ...(session.id === sessionId ? style.selected : {}) },
|
|
@@ -1142,20 +1176,55 @@ function RemoteSessionPage({ api }) {
|
|
|
1142
1176
|
},
|
|
1143
1177
|
sessionStatus === 'running' ? h('span', { className: 'mai-pulse', style: { ...style.dot, background: t.accent } }) : null,
|
|
1144
1178
|
SESSION_STATUS_LABEL[sessionStatus]) : null,
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1179
|
+
(() => {
|
|
1180
|
+
const modelPickerDisabled = !hasControl || switchingModel || models.length === 0
|
|
1181
|
+
const currentModelInfo = models.find(m => m.id === currentModel)
|
|
1182
|
+
const currentModelUnknown = Boolean(currentModel && !currentModelInfo)
|
|
1183
|
+
const needle = modelFilter.trim().toLowerCase()
|
|
1184
|
+
const filteredModels = needle === ''
|
|
1185
|
+
? models
|
|
1186
|
+
: models.filter(m => `${m.displayName} ${m.model ?? ''} ${m.id}`.toLowerCase().includes(needle))
|
|
1187
|
+
return h('details', {
|
|
1188
|
+
className: 'mai-model-picker',
|
|
1189
|
+
style: { ...style.modelPicker, ...(modelPickerDisabled ? { opacity: 0.5, pointerEvents: 'none' } : {}) },
|
|
1190
|
+
ref: modelPickerRef,
|
|
1153
1191
|
},
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1192
|
+
h('summary', { style: style.modelPickerSummary, title: '切换该会话的模型' },
|
|
1193
|
+
h('span', { className: 'mai-model-picker-caret', style: { ...style.modelPickerCaret, flexShrink: 0 } }, '▾'),
|
|
1194
|
+
h('span', { style: { flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', textAlign: 'left' } },
|
|
1195
|
+
currentModelInfo
|
|
1196
|
+
? `${currentModelInfo.displayName} (${Math.round(currentModelInfo.contextWindowTokens / 1000)}k)`
|
|
1197
|
+
: currentModel ? currentModel.slice(0, 24) : '会话模型')),
|
|
1198
|
+
h('div', { className: 'mai-model-menu', style: style.modelMenu },
|
|
1199
|
+
models.length > 6 ? h('input', {
|
|
1200
|
+
type: 'search', placeholder: '搜索模型…', 'aria-label': '搜索模型', value: modelFilter,
|
|
1201
|
+
style: style.modelSearch,
|
|
1202
|
+
onChange: event => setModelFilter(event.target.value),
|
|
1203
|
+
}) : null,
|
|
1204
|
+
h('div', { className: 'mai-model-picker-menu', style: style.modelMenuList },
|
|
1205
|
+
currentModelUnknown && (needle === '' || currentModel.toLowerCase().includes(needle))
|
|
1206
|
+
? h('button', {
|
|
1207
|
+
type: 'button', disabled: true, 'aria-pressed': true,
|
|
1208
|
+
style: { ...style.modelItem, ...style.modelItemActive },
|
|
1209
|
+
},
|
|
1210
|
+
h('span', { style: style.modelItemName }, currentModel.slice(0, 24)),
|
|
1211
|
+
h('span', { style: style.modelItemMeta }, '当前模型'))
|
|
1212
|
+
: null,
|
|
1213
|
+
filteredModels.length === 0 ? h('div', { style: style.modelEmpty }, '没有匹配的模型') : null,
|
|
1214
|
+
filteredModels.map(m => h('button', {
|
|
1215
|
+
key: m.id, type: 'button',
|
|
1216
|
+
'aria-pressed': m.id === currentModel,
|
|
1217
|
+
style: { ...style.modelItem, ...(m.id === currentModel ? style.modelItemActive : {}) },
|
|
1218
|
+
disabled: !hasControl || switchingModel,
|
|
1219
|
+
title: m.model && m.model !== m.displayName ? m.model : undefined,
|
|
1220
|
+
onClick: () => {
|
|
1221
|
+
if (m.id !== currentModel) switchSessionModel(m.id)
|
|
1222
|
+
modelPickerRef.current?.removeAttribute('open')
|
|
1223
|
+
},
|
|
1224
|
+
},
|
|
1225
|
+
h('span', { style: style.modelItemName }, m.displayName),
|
|
1226
|
+
h('span', { style: style.modelItemMeta }, `${Math.round(m.contextWindowTokens / 1000)}k`))))))
|
|
1227
|
+
})()),
|
|
1159
1228
|
h('div', { style: style.events, className: 'mai-thread-events mai-events', 'aria-live': 'polite', 'data-events': '', ref: eventsRef,
|
|
1160
1229
|
onScroll: event => {
|
|
1161
1230
|
const el = event.currentTarget
|
|
@@ -739,7 +739,9 @@ export class MonkeyAIRemoteService extends TypertRemoteService {
|
|
|
739
739
|
const emitHistoryLoaded = model => {
|
|
740
740
|
if (historyLoadedEmitted) return
|
|
741
741
|
historyLoadedEmitted = true
|
|
742
|
-
|
|
742
|
+
// model travels inside data: the RemoteFrame schema only passes through
|
|
743
|
+
// declared top-level fields, so a bare `model` key would be stripped.
|
|
744
|
+
emit({ type: 'history_loaded', sessionId: selectedSession, ...(model === undefined ? {} : { data: { model } }) })
|
|
743
745
|
}
|
|
744
746
|
const refreshPersistedHistory = async changedMessageId => {
|
|
745
747
|
if (!ticketHttpBase || !ticketValue) return
|
|
@@ -1055,6 +1057,21 @@ export class MonkeyAIRemoteService extends TypertRemoteService {
|
|
|
1055
1057
|
ticketHttpBase = ticket.httpBase
|
|
1056
1058
|
if (signal.aborted) throw signal.reason ?? new Error('远程会话已取消')
|
|
1057
1059
|
|
|
1060
|
+
// Surface the session's current model as early as possible. Large
|
|
1061
|
+
// histories paginate slowly and the stream carrier can drop mid-walk;
|
|
1062
|
+
// a one-message read already carries session.model, so the model picker
|
|
1063
|
+
// syncs before (and independently of) the history walk.
|
|
1064
|
+
try {
|
|
1065
|
+
const probeTimeout = AbortSignal.any([AbortSignal.timeout(20_000), ...(signal ? [signal] : [])])
|
|
1066
|
+
const modelProbe = await readSessionHistoryPage({ httpBase: ticket.httpBase, ticket: ticketValue, sessionId: selectedSession, limit: 1, signal: probeTimeout })
|
|
1067
|
+
if (typeof modelProbe.session?.model === 'string' && modelProbe.session.model !== '') {
|
|
1068
|
+
yield { type: 'notification', sessionId: selectedSession, method: '_monkeyai/session/model', data: { model: modelProbe.session.model } }
|
|
1069
|
+
}
|
|
1070
|
+
} catch {
|
|
1071
|
+
// Best-effort: history_loaded still carries the model when its walk
|
|
1072
|
+
// completes, and the picker shows the placeholder meanwhile.
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1058
1075
|
// Load the full history over the runtime's HTTP read channel before the
|
|
1059
1076
|
// ACP connection starts. The ACP session/load replay only streams a
|
|
1060
1077
|
// small recent window and the socket is closed mid-replay; the HTTP
|
|
@@ -1071,7 +1088,10 @@ export class MonkeyAIRemoteService extends TypertRemoteService {
|
|
|
1071
1088
|
if (history.count > 0) {
|
|
1072
1089
|
httpHistoryComplete = true
|
|
1073
1090
|
httpConfigurationRevision = history.configurationRevision
|
|
1074
|
-
|
|
1091
|
+
// The runtime's session/load response omits the current model; the
|
|
1092
|
+
// history channel's session object carries it, so surface it with
|
|
1093
|
+
// history_loaded to preselect the right entry in the model picker.
|
|
1094
|
+
emitHistoryLoaded(history.sessionModel)
|
|
1075
1095
|
}
|
|
1076
1096
|
} catch (historyError) {
|
|
1077
1097
|
if (signal?.aborted) throw historyError
|
|
@@ -1518,12 +1538,16 @@ async function fetchFullSessionHistory({ httpBase, ticket, sessionId, signal, on
|
|
|
1518
1538
|
const toolCallsById = {}
|
|
1519
1539
|
let cursor
|
|
1520
1540
|
let configurationRevision
|
|
1541
|
+
let sessionModel
|
|
1521
1542
|
for (let page = 0; page < maxPages; page++) {
|
|
1522
1543
|
const timeout = AbortSignal.any([AbortSignal.timeout(30_000), ...(signal ? [signal] : [])])
|
|
1523
1544
|
const result = await readSessionHistoryPage({ httpBase, ticket, sessionId, cursor, direction: 'older', signal: timeout })
|
|
1524
1545
|
if (Number.isSafeInteger(result.session?.configurationRevision)) {
|
|
1525
1546
|
configurationRevision = result.session.configurationRevision
|
|
1526
1547
|
}
|
|
1548
|
+
if (typeof result.session?.model === 'string' && result.session.model !== '') {
|
|
1549
|
+
sessionModel = result.session.model
|
|
1550
|
+
}
|
|
1527
1551
|
const pageMessages = []
|
|
1528
1552
|
for (const message of Array.isArray(result.messages) ? result.messages : []) {
|
|
1529
1553
|
if (typeof message?.id !== 'string' || message.id === '' || seenIds.has(message.id)) continue
|
|
@@ -1540,7 +1564,7 @@ async function fetchFullSessionHistory({ httpBase, ticket, sessionId, signal, on
|
|
|
1540
1564
|
for (const frame of historyMessageToFrames(sessionId, message, toolCallsById)) await onFrame(frame)
|
|
1541
1565
|
}
|
|
1542
1566
|
}
|
|
1543
|
-
return { messageIds: [...seenIds], count: seenIds.size, configurationRevision }
|
|
1567
|
+
return { messageIds: [...seenIds], count: seenIds.size, configurationRevision, sessionModel }
|
|
1544
1568
|
}
|
|
1545
1569
|
|
|
1546
1570
|
// Refresh only the newest pages after a _monkeyai/session/history_changed
|