dsh-monkeyai-sessions 0.1.13 → 0.1.15
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 +106 -18
- package/lib/client-runtime.cjs +106 -18
- package/package.json +1 -1
- package/src/client/index.js +84 -14
- package/src/host/monkeyai-remote.js +38 -5
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.15.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.15.tgz
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
已在真实的 DSH `0.2.2-rc.1` web profile 与在线 MonkeyAI runtime 上端到端验证:授权、完整历史加载、指令下发、模型切换、回合取消均正常工作。
|
package/client.js
CHANGED
|
@@ -586,7 +586,18 @@ window.__ModuleLoader__.load({
|
|
|
586
586
|
composer: { display: "flex", gap: 8, alignItems: "flex-end" },
|
|
587
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" },
|
|
588
588
|
details: { fontSize: 12, color: t.text2 },
|
|
589
|
-
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" }
|
|
590
601
|
};
|
|
591
602
|
var PANEL_CSS = `
|
|
592
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; }
|
|
@@ -603,6 +614,10 @@ window.__ModuleLoader__.load({
|
|
|
603
614
|
.mai-panel .mai-card-hover { transition: border-color .15s, background .15s; }
|
|
604
615
|
.mai-panel .mai-card-hover:not(:disabled):hover { border-color: #b9cdf3 !important; background: #f7faff !important; }
|
|
605
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); }
|
|
606
621
|
.mai-panel .mai-workspace { display: grid; grid-template-columns: minmax(248px, 292px) minmax(0, 1fr); gap: 12px; min-width: 0; align-items: stretch; }
|
|
607
622
|
.mai-panel .mai-session-sidebar { min-height: min(70vh, 720px); }
|
|
608
623
|
.mai-panel .mai-session-list { scrollbar-gutter: stable; }
|
|
@@ -624,6 +639,9 @@ window.__ModuleLoader__.load({
|
|
|
624
639
|
.mai-panel .mai-page-title { font-size: 16px !important; }
|
|
625
640
|
.mai-panel .mai-toolbar-controls { align-items: stretch; }
|
|
626
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; }
|
|
627
645
|
.mai-panel .mai-session-list { grid-template-columns: 1fr !important; max-height: 210px; }
|
|
628
646
|
.mai-panel .mai-thread { height: 68vh; min-height: 360px; padding: 10px; }
|
|
629
647
|
.mai-panel .mai-thread-header { gap: 6px !important; }
|
|
@@ -657,7 +675,7 @@ window.__ModuleLoader__.load({
|
|
|
657
675
|
});
|
|
658
676
|
}
|
|
659
677
|
function isRemoteStreamCarrierError(error) {
|
|
660
|
-
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));
|
|
661
679
|
}
|
|
662
680
|
function renderMarkdown(text) {
|
|
663
681
|
if (typeof text !== "string" || text === "") return null;
|
|
@@ -876,6 +894,7 @@ window.__ModuleLoader__.load({
|
|
|
876
894
|
const [sessionStatus, setSessionStatus] = useState("");
|
|
877
895
|
const [signInEmail, setSignInEmail] = useState("");
|
|
878
896
|
const [signInPassword, setSignInPassword] = useState("");
|
|
897
|
+
const [modelFilter, setModelFilter] = useState("");
|
|
879
898
|
const streamRef = useRef(null);
|
|
880
899
|
const historyLoadedRef = useRef(false);
|
|
881
900
|
const lastDraftRef = useRef("");
|
|
@@ -883,6 +902,16 @@ window.__ModuleLoader__.load({
|
|
|
883
902
|
const eventsRef = useRef(null);
|
|
884
903
|
const prevScrollHeightRef = useRef(null);
|
|
885
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
|
+
}, []);
|
|
886
915
|
const title = useMemo(() => {
|
|
887
916
|
const runtime = runtimeSessions.find((item) => item.id === sessionId);
|
|
888
917
|
const summary = summarySessions.find((item) => item.id === sessionId);
|
|
@@ -1129,6 +1158,7 @@ window.__ModuleLoader__.load({
|
|
|
1129
1158
|
setError("");
|
|
1130
1159
|
setAnswerText("");
|
|
1131
1160
|
setCurrentModel("");
|
|
1161
|
+
setModelFilter("");
|
|
1132
1162
|
setVisibleCount(RECENT_LIMIT);
|
|
1133
1163
|
setHistoryLoading(true);
|
|
1134
1164
|
setStatus("\u6B63\u5728\u8FDE\u63A5\u8FDC\u7A0B\u4F1A\u8BDD");
|
|
@@ -1152,7 +1182,7 @@ window.__ModuleLoader__.load({
|
|
|
1152
1182
|
setStatus("\u5386\u53F2\u5DF2\u52A0\u8F7D");
|
|
1153
1183
|
setHistoryLoading(false);
|
|
1154
1184
|
setSessionStatus((current) => current || "idle");
|
|
1155
|
-
if (frame.model) setCurrentModel(frame.model);
|
|
1185
|
+
if (frame.data?.model) setCurrentModel(frame.data.model);
|
|
1156
1186
|
}
|
|
1157
1187
|
if (frame.type === "history_incomplete") {
|
|
1158
1188
|
setStatus("\u5386\u53F2\u4E0D\u5B8C\u6574\uFF1A" + (frame.message || "\u56DE\u653E\u88AB\u4E2D\u65AD"));
|
|
@@ -1176,6 +1206,9 @@ window.__ModuleLoader__.load({
|
|
|
1176
1206
|
if (frame.type === "request") {
|
|
1177
1207
|
setPendingRequests((current) => [...current.filter((item) => item.requestId !== frame.requestId), frame]);
|
|
1178
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
|
+
}
|
|
1179
1212
|
if (frame.type === "notification" && frame.method === "_monkeyai/session/state") {
|
|
1180
1213
|
const status2 = frame.data?.state?.status;
|
|
1181
1214
|
if (typeof status2 === "string" && SESSION_STATUS_LABEL[status2]) {
|
|
@@ -1808,21 +1841,76 @@ window.__ModuleLoader__.load({
|
|
|
1808
1841
|
sessionStatus === "running" ? h("span", { className: "mai-pulse", style: { ...style.dot, background: t.accent } }) : null,
|
|
1809
1842
|
SESSION_STATUS_LABEL[sessionStatus]
|
|
1810
1843
|
) : null,
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
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
|
+
})()
|
|
1826
1914
|
),
|
|
1827
1915
|
h(
|
|
1828
1916
|
"div",
|
package/lib/client-runtime.cjs
CHANGED
|
@@ -581,7 +581,18 @@ var style = {
|
|
|
581
581
|
composer: { display: "flex", gap: 8, alignItems: "flex-end" },
|
|
582
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" },
|
|
583
583
|
details: { fontSize: 12, color: t.text2 },
|
|
584
|
-
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" }
|
|
585
596
|
};
|
|
586
597
|
var PANEL_CSS = `
|
|
587
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; }
|
|
@@ -598,6 +609,10 @@ var PANEL_CSS = `
|
|
|
598
609
|
.mai-panel .mai-card-hover { transition: border-color .15s, background .15s; }
|
|
599
610
|
.mai-panel .mai-card-hover:not(:disabled):hover { border-color: #b9cdf3 !important; background: #f7faff !important; }
|
|
600
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); }
|
|
601
616
|
.mai-panel .mai-workspace { display: grid; grid-template-columns: minmax(248px, 292px) minmax(0, 1fr); gap: 12px; min-width: 0; align-items: stretch; }
|
|
602
617
|
.mai-panel .mai-session-sidebar { min-height: min(70vh, 720px); }
|
|
603
618
|
.mai-panel .mai-session-list { scrollbar-gutter: stable; }
|
|
@@ -619,6 +634,9 @@ var PANEL_CSS = `
|
|
|
619
634
|
.mai-panel .mai-page-title { font-size: 16px !important; }
|
|
620
635
|
.mai-panel .mai-toolbar-controls { align-items: stretch; }
|
|
621
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; }
|
|
622
640
|
.mai-panel .mai-session-list { grid-template-columns: 1fr !important; max-height: 210px; }
|
|
623
641
|
.mai-panel .mai-thread { height: 68vh; min-height: 360px; padding: 10px; }
|
|
624
642
|
.mai-panel .mai-thread-header { gap: 6px !important; }
|
|
@@ -652,7 +670,7 @@ function withTimeout(promise, timeoutMs, label) {
|
|
|
652
670
|
});
|
|
653
671
|
}
|
|
654
672
|
function isRemoteStreamCarrierError(error) {
|
|
655
|
-
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));
|
|
656
674
|
}
|
|
657
675
|
function renderMarkdown(text) {
|
|
658
676
|
if (typeof text !== "string" || text === "") return null;
|
|
@@ -871,6 +889,7 @@ function RemoteSessionPage({ api }) {
|
|
|
871
889
|
const [sessionStatus, setSessionStatus] = useState("");
|
|
872
890
|
const [signInEmail, setSignInEmail] = useState("");
|
|
873
891
|
const [signInPassword, setSignInPassword] = useState("");
|
|
892
|
+
const [modelFilter, setModelFilter] = useState("");
|
|
874
893
|
const streamRef = useRef(null);
|
|
875
894
|
const historyLoadedRef = useRef(false);
|
|
876
895
|
const lastDraftRef = useRef("");
|
|
@@ -878,6 +897,16 @@ function RemoteSessionPage({ api }) {
|
|
|
878
897
|
const eventsRef = useRef(null);
|
|
879
898
|
const prevScrollHeightRef = useRef(null);
|
|
880
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
|
+
}, []);
|
|
881
910
|
const title = useMemo(() => {
|
|
882
911
|
const runtime = runtimeSessions.find((item) => item.id === sessionId);
|
|
883
912
|
const summary = summarySessions.find((item) => item.id === sessionId);
|
|
@@ -1124,6 +1153,7 @@ function RemoteSessionPage({ api }) {
|
|
|
1124
1153
|
setError("");
|
|
1125
1154
|
setAnswerText("");
|
|
1126
1155
|
setCurrentModel("");
|
|
1156
|
+
setModelFilter("");
|
|
1127
1157
|
setVisibleCount(RECENT_LIMIT);
|
|
1128
1158
|
setHistoryLoading(true);
|
|
1129
1159
|
setStatus("\u6B63\u5728\u8FDE\u63A5\u8FDC\u7A0B\u4F1A\u8BDD");
|
|
@@ -1147,7 +1177,7 @@ function RemoteSessionPage({ api }) {
|
|
|
1147
1177
|
setStatus("\u5386\u53F2\u5DF2\u52A0\u8F7D");
|
|
1148
1178
|
setHistoryLoading(false);
|
|
1149
1179
|
setSessionStatus((current) => current || "idle");
|
|
1150
|
-
if (frame.model) setCurrentModel(frame.model);
|
|
1180
|
+
if (frame.data?.model) setCurrentModel(frame.data.model);
|
|
1151
1181
|
}
|
|
1152
1182
|
if (frame.type === "history_incomplete") {
|
|
1153
1183
|
setStatus("\u5386\u53F2\u4E0D\u5B8C\u6574\uFF1A" + (frame.message || "\u56DE\u653E\u88AB\u4E2D\u65AD"));
|
|
@@ -1171,6 +1201,9 @@ function RemoteSessionPage({ api }) {
|
|
|
1171
1201
|
if (frame.type === "request") {
|
|
1172
1202
|
setPendingRequests((current) => [...current.filter((item) => item.requestId !== frame.requestId), frame]);
|
|
1173
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
|
+
}
|
|
1174
1207
|
if (frame.type === "notification" && frame.method === "_monkeyai/session/state") {
|
|
1175
1208
|
const status2 = frame.data?.state?.status;
|
|
1176
1209
|
if (typeof status2 === "string" && SESSION_STATUS_LABEL[status2]) {
|
|
@@ -1803,21 +1836,76 @@ function RemoteSessionPage({ api }) {
|
|
|
1803
1836
|
sessionStatus === "running" ? h("span", { className: "mai-pulse", style: { ...style.dot, background: t.accent } }) : null,
|
|
1804
1837
|
SESSION_STATUS_LABEL[sessionStatus]
|
|
1805
1838
|
) : null,
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
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
|
+
})()
|
|
1821
1909
|
),
|
|
1822
1910
|
h(
|
|
1823
1911
|
"div",
|
package/package.json
CHANGED
package/src/client/index.js
CHANGED
|
@@ -72,6 +72,17 @@ const style = {
|
|
|
72
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' },
|
|
73
73
|
details: { fontSize: 12, color: t.text2 },
|
|
74
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' },
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
const PANEL_CSS = `
|
|
@@ -89,6 +100,10 @@ const PANEL_CSS = `
|
|
|
89
100
|
.mai-panel .mai-card-hover { transition: border-color .15s, background .15s; }
|
|
90
101
|
.mai-panel .mai-card-hover:not(:disabled):hover { border-color: #b9cdf3 !important; background: #f7faff !important; }
|
|
91
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); }
|
|
92
107
|
.mai-panel .mai-workspace { display: grid; grid-template-columns: minmax(248px, 292px) minmax(0, 1fr); gap: 12px; min-width: 0; align-items: stretch; }
|
|
93
108
|
.mai-panel .mai-session-sidebar { min-height: min(70vh, 720px); }
|
|
94
109
|
.mai-panel .mai-session-list { scrollbar-gutter: stable; }
|
|
@@ -110,6 +125,9 @@ const PANEL_CSS = `
|
|
|
110
125
|
.mai-panel .mai-page-title { font-size: 16px !important; }
|
|
111
126
|
.mai-panel .mai-toolbar-controls { align-items: stretch; }
|
|
112
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; }
|
|
113
131
|
.mai-panel .mai-session-list { grid-template-columns: 1fr !important; max-height: 210px; }
|
|
114
132
|
.mai-panel .mai-thread { height: 68vh; min-height: 360px; padding: 10px; }
|
|
115
133
|
.mai-panel .mai-thread-header { gap: 6px !important; }
|
|
@@ -141,6 +159,7 @@ function withTimeout(promise, timeoutMs, label) {
|
|
|
141
159
|
|
|
142
160
|
function isRemoteStreamCarrierError(error) {
|
|
143
161
|
return /api gateway: remote stream websocket (?:failed|closed)/i.test(errorText(error))
|
|
162
|
+
|| /remote stream socket closed/i.test(errorText(error))
|
|
144
163
|
}
|
|
145
164
|
|
|
146
165
|
// Lightweight inline markdown: fenced code blocks, `code`, **bold**,
|
|
@@ -342,6 +361,7 @@ function RemoteSessionPage({ api }) {
|
|
|
342
361
|
const [sessionStatus, setSessionStatus] = useState('')
|
|
343
362
|
const [signInEmail, setSignInEmail] = useState('')
|
|
344
363
|
const [signInPassword, setSignInPassword] = useState('')
|
|
364
|
+
const [modelFilter, setModelFilter] = useState('')
|
|
345
365
|
const streamRef = useRef(null)
|
|
346
366
|
const historyLoadedRef = useRef(false)
|
|
347
367
|
const lastDraftRef = useRef('')
|
|
@@ -349,6 +369,17 @@ function RemoteSessionPage({ api }) {
|
|
|
349
369
|
const eventsRef = useRef(null)
|
|
350
370
|
const prevScrollHeightRef = useRef(null)
|
|
351
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
|
+
}, [])
|
|
352
383
|
const title = useMemo(() => {
|
|
353
384
|
const runtime = runtimeSessions.find(item => item.id === sessionId)
|
|
354
385
|
const summary = summarySessions.find(item => item.id === sessionId)
|
|
@@ -605,6 +636,7 @@ function RemoteSessionPage({ api }) {
|
|
|
605
636
|
setError('')
|
|
606
637
|
setAnswerText('')
|
|
607
638
|
setCurrentModel('')
|
|
639
|
+
setModelFilter('')
|
|
608
640
|
setVisibleCount(RECENT_LIMIT)
|
|
609
641
|
setHistoryLoading(true)
|
|
610
642
|
setStatus('正在连接远程会话')
|
|
@@ -628,7 +660,7 @@ function RemoteSessionPage({ api }) {
|
|
|
628
660
|
setStatus('历史已加载')
|
|
629
661
|
setHistoryLoading(false)
|
|
630
662
|
setSessionStatus(current => current || 'idle')
|
|
631
|
-
if (frame.model) setCurrentModel(frame.model)
|
|
663
|
+
if (frame.data?.model) setCurrentModel(frame.data.model)
|
|
632
664
|
}
|
|
633
665
|
if (frame.type === 'history_incomplete') {
|
|
634
666
|
setStatus('历史不完整:' + (frame.message || '回放被中断'))
|
|
@@ -652,6 +684,9 @@ function RemoteSessionPage({ api }) {
|
|
|
652
684
|
if (frame.type === 'request') {
|
|
653
685
|
setPendingRequests(current => [...current.filter(item => item.requestId !== frame.requestId), frame])
|
|
654
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
|
+
}
|
|
655
690
|
if (frame.type === 'notification' && frame.method === '_monkeyai/session/state') {
|
|
656
691
|
const status = frame.data?.state?.status
|
|
657
692
|
if (typeof status === 'string' && SESSION_STATUS_LABEL[status]) {
|
|
@@ -1141,20 +1176,55 @@ function RemoteSessionPage({ api }) {
|
|
|
1141
1176
|
},
|
|
1142
1177
|
sessionStatus === 'running' ? h('span', { className: 'mai-pulse', style: { ...style.dot, background: t.accent } }) : null,
|
|
1143
1178
|
SESSION_STATUS_LABEL[sessionStatus]) : null,
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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,
|
|
1152
1191
|
},
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
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
|
+
})()),
|
|
1158
1228
|
h('div', { style: style.events, className: 'mai-thread-events mai-events', 'aria-live': 'polite', 'data-events': '', ref: eventsRef,
|
|
1159
1229
|
onScroll: event => {
|
|
1160
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,23 +1057,50 @@ 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
|
|
1061
|
-
// endpoint paginates the complete message log by cursor instead.
|
|
1078
|
+
// endpoint paginates the complete message log by cursor instead. Yield
|
|
1079
|
+
// the completed history before ACP session/load so a slow runtime mount
|
|
1080
|
+
// cannot keep the panel's history spinner active.
|
|
1062
1081
|
try {
|
|
1082
|
+
const historyFrames = []
|
|
1063
1083
|
const history = await fetchFullSessionHistory({
|
|
1064
1084
|
httpBase: ticket.httpBase,
|
|
1065
1085
|
ticket: ticketValue,
|
|
1066
1086
|
sessionId: selectedSession,
|
|
1067
1087
|
signal,
|
|
1068
|
-
onFrame:
|
|
1088
|
+
onFrame: frame => { historyFrames.push(frame) },
|
|
1069
1089
|
})
|
|
1090
|
+
for (const frame of historyFrames) yield frame
|
|
1070
1091
|
for (const id of history.messageIds) httpHistoryMessageIds.add(id)
|
|
1071
1092
|
if (history.count > 0) {
|
|
1072
1093
|
httpHistoryComplete = true
|
|
1073
1094
|
httpConfigurationRevision = history.configurationRevision
|
|
1074
|
-
|
|
1095
|
+
// The runtime's session/load response omits the current model; the
|
|
1096
|
+
// history channel's session object carries it, so surface it with
|
|
1097
|
+
// history_loaded to preselect the right entry in the model picker.
|
|
1098
|
+
historyLoadedEmitted = true
|
|
1099
|
+
yield {
|
|
1100
|
+
type: 'history_loaded',
|
|
1101
|
+
sessionId: selectedSession,
|
|
1102
|
+
...(history.sessionModel === undefined ? {} : { data: { model: history.sessionModel } }),
|
|
1103
|
+
}
|
|
1075
1104
|
}
|
|
1076
1105
|
} catch (historyError) {
|
|
1077
1106
|
if (signal?.aborted) throw historyError
|
|
@@ -1518,12 +1547,16 @@ async function fetchFullSessionHistory({ httpBase, ticket, sessionId, signal, on
|
|
|
1518
1547
|
const toolCallsById = {}
|
|
1519
1548
|
let cursor
|
|
1520
1549
|
let configurationRevision
|
|
1550
|
+
let sessionModel
|
|
1521
1551
|
for (let page = 0; page < maxPages; page++) {
|
|
1522
1552
|
const timeout = AbortSignal.any([AbortSignal.timeout(30_000), ...(signal ? [signal] : [])])
|
|
1523
1553
|
const result = await readSessionHistoryPage({ httpBase, ticket, sessionId, cursor, direction: 'older', signal: timeout })
|
|
1524
1554
|
if (Number.isSafeInteger(result.session?.configurationRevision)) {
|
|
1525
1555
|
configurationRevision = result.session.configurationRevision
|
|
1526
1556
|
}
|
|
1557
|
+
if (typeof result.session?.model === 'string' && result.session.model !== '') {
|
|
1558
|
+
sessionModel = result.session.model
|
|
1559
|
+
}
|
|
1527
1560
|
const pageMessages = []
|
|
1528
1561
|
for (const message of Array.isArray(result.messages) ? result.messages : []) {
|
|
1529
1562
|
if (typeof message?.id !== 'string' || message.id === '' || seenIds.has(message.id)) continue
|
|
@@ -1540,7 +1573,7 @@ async function fetchFullSessionHistory({ httpBase, ticket, sessionId, signal, on
|
|
|
1540
1573
|
for (const frame of historyMessageToFrames(sessionId, message, toolCallsById)) await onFrame(frame)
|
|
1541
1574
|
}
|
|
1542
1575
|
}
|
|
1543
|
-
return { messageIds: [...seenIds], count: seenIds.size, configurationRevision }
|
|
1576
|
+
return { messageIds: [...seenIds], count: seenIds.size, configurationRevision, sessionModel }
|
|
1544
1577
|
}
|
|
1545
1578
|
|
|
1546
1579
|
// Refresh only the newest pages after a _monkeyai/session/history_changed
|