dsh-miloco-bridge 0.1.0 → 0.1.2
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 +18 -10
- package/cordis.patch.yml +80 -5
- package/{doc → docs}/ARCHITECTURE.md +13 -6
- package/{doc → docs}/INSTALL.md +25 -11
- package/{doc → docs}/SKILL-REWRITES.md +3 -3
- package/{doc → docs}/TOOLS.md +13 -10
- package/{doc → docs}/VERIFICATION.md +22 -6
- package/docs/images/agent-presets.png +0 -0
- package/docs/images/miloco-settings.png +0 -0
- package/lib/client.js +693 -119
- package/lib/deploy.js +236 -38
- package/lib/github-release.js +162 -0
- package/lib/index.js +11 -1
- package/lib/settings-api.js +170 -1
- package/lib/tools.js +365 -40
- package/package.json +2 -2
- package/skills/miloco-create-task/SKILL.md +63 -14
- package/skills/miloco-devices/SKILL.md +3 -2
- package/doc/images/agent-presets.png +0 -0
- package/doc/images/miloco-settings.png +0 -0
package/lib/client.js
CHANGED
|
@@ -40,55 +40,202 @@ window.__ModuleLoader__.load({
|
|
|
40
40
|
const style = document.createElement('style')
|
|
41
41
|
style.id = STYLE_ID
|
|
42
42
|
style.textContent = `
|
|
43
|
-
.dsh-miloco-bridge-settings {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
.dsh-miloco-bridge-settings {
|
|
44
|
+
--mc-border: var(--dsw-alias-border-l2, rgba(128,128,128,.24));
|
|
45
|
+
--mc-border-soft: var(--dsw-alias-border-l1, rgba(128,128,128,.14));
|
|
46
|
+
--mc-bg: var(--dsw-alias-bg-l1, rgba(128,128,128,.035));
|
|
47
|
+
--mc-bg-soft: var(--dsw-alias-bg-l2, rgba(128,128,128,.07));
|
|
48
|
+
--mc-text: var(--dsw-alias-label-primary, inherit);
|
|
49
|
+
--mc-text-2: var(--dsw-alias-label-secondary, rgba(128,128,128,.95));
|
|
50
|
+
--mc-text-3: var(--dsw-alias-label-tertiary, rgba(128,128,128,.72));
|
|
51
|
+
--mc-accent: var(--dsw-alias-brand-primary, #4a7edc);
|
|
52
|
+
--mc-success: var(--dsw-alias-state-success-primary, #3c9a5a);
|
|
53
|
+
--mc-error: var(--dsw-alias-state-error-primary, #c85a50);
|
|
54
|
+
font-size: 13px; line-height: 1.65; max-width: 820px; color: var(--mc-text);
|
|
55
|
+
}
|
|
56
|
+
.dsh-miloco-bridge-settings h3 { font-size: 14px; margin: 0 0 10px; font-weight: 600; letter-spacing: .01em; }
|
|
57
|
+
.dsh-miloco-bridge-settings .cardHead { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 2px; }
|
|
58
|
+
.dsh-miloco-bridge-settings .cardHead h3 { margin: 0; }
|
|
59
|
+
.dsh-miloco-bridge-settings .iconBtn {
|
|
60
|
+
flex: none; display: inline-flex; align-items: center; justify-content: center;
|
|
61
|
+
width: 26px; height: 26px; padding: 0; border: 0; border-radius: 6px;
|
|
62
|
+
background: transparent; color: var(--mc-text-3);
|
|
63
|
+
}
|
|
64
|
+
.dsh-miloco-bridge-settings .iconBtn:hover:not(:disabled) {
|
|
65
|
+
background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); border-color: transparent;
|
|
66
|
+
}
|
|
67
|
+
.dsh-miloco-bridge-settings .iconBtn.spinning svg { animation: dsh-miloco-spin .9s linear infinite; }
|
|
68
|
+
@keyframes dsh-miloco-spin { to { transform: rotate(360deg); } }
|
|
69
|
+
.dsh-miloco-bridge-settings .card {
|
|
70
|
+
border: 1px solid var(--mc-border); border-radius: 10px; padding: 15px 16px;
|
|
71
|
+
margin-bottom: 12px; background: transparent;
|
|
72
|
+
}
|
|
73
|
+
.dsh-miloco-bridge-settings .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; margin-top: 10px; }
|
|
74
|
+
.dsh-miloco-bridge-settings .stat {
|
|
75
|
+
display: flex; flex-direction: column; padding: 9px 11px; border-radius: 8px;
|
|
76
|
+
background: transparent; border: 1px solid var(--mc-border-soft);
|
|
77
|
+
}
|
|
78
|
+
.dsh-miloco-bridge-settings .stat .k { color: var(--mc-text-3); font-size: 11px; }
|
|
79
|
+
.dsh-miloco-bridge-settings .stat .v { font-size: 17px; font-weight: 600; line-height: 1.35; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
50
80
|
.dsh-miloco-bridge-settings .row { display: flex; align-items: center; gap: 8px; margin: 8px 0; flex-wrap: wrap; }
|
|
51
|
-
.dsh-miloco-bridge-settings .row > label { min-width: 96px;
|
|
52
|
-
.dsh-miloco-bridge-settings
|
|
53
|
-
|
|
54
|
-
|
|
81
|
+
.dsh-miloco-bridge-settings .row > label { min-width: 96px; color: var(--mc-text-2); }
|
|
82
|
+
.dsh-miloco-bridge-settings .field { display: flex; flex-direction: column; gap: 4px; margin: 10px 0; }
|
|
83
|
+
.dsh-miloco-bridge-settings .formGrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 4px 14px; margin: 10px 0; }
|
|
84
|
+
.dsh-miloco-bridge-settings .formGrid .field { margin: 0; }
|
|
85
|
+
.dsh-miloco-bridge-settings .field > label { color: var(--mc-text-2); font-size: 12px; }
|
|
86
|
+
.dsh-miloco-bridge-settings .fieldRow { display: flex; align-items: center; gap: 8px; }
|
|
87
|
+
.dsh-miloco-bridge-settings .fieldRow input:not([type="checkbox"]):not([type="radio"]) { flex: 1 1 auto; width: auto; min-width: 0; }
|
|
88
|
+
.dsh-miloco-bridge-settings .row > select,
|
|
89
|
+
.dsh-miloco-bridge-settings .row > input:not([type="checkbox"]):not([type="radio"]) { flex: 1 1 220px; width: auto; }
|
|
90
|
+
.dsh-miloco-bridge-settings .statusRow {
|
|
91
|
+
display: grid; grid-template-columns: 96px minmax(0, 1fr) auto; align-items: center;
|
|
92
|
+
gap: 10px; padding: 7px 0; border-bottom: 1px solid var(--mc-border-soft);
|
|
93
|
+
}
|
|
94
|
+
.dsh-miloco-bridge-settings .statusRow:last-of-type { border-bottom: 0; }
|
|
95
|
+
.dsh-miloco-bridge-settings .statusLabel { color: var(--mc-text-2); }
|
|
96
|
+
.dsh-miloco-bridge-settings .statusValue { min-width: 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
97
|
+
.dsh-miloco-bridge-settings .statusCheck { justify-self: end; color: var(--mc-success); font-weight: 700; font-size: 14px; line-height: 1; }
|
|
98
|
+
.dsh-miloco-bridge-settings .statusEnd { justify-self: end; }
|
|
99
|
+
.dsh-miloco-bridge-settings .statusValue > a, .dsh-miloco-bridge-settings .statusValue > code {
|
|
100
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
|
|
101
|
+
}
|
|
102
|
+
.dsh-miloco-bridge-settings select,
|
|
103
|
+
.dsh-miloco-bridge-settings input:not([type="checkbox"]):not([type="radio"]) {
|
|
104
|
+
flex: 0 0 auto; width: 100%; height: 34px; box-sizing: border-box;
|
|
105
|
+
padding: 6px 9px; border-radius: 7px; border: 1px solid var(--mc-border);
|
|
106
|
+
background: var(--dsw-alias-bg-base, transparent); color: inherit; font: inherit; outline: none;
|
|
107
|
+
}
|
|
108
|
+
.dsh-miloco-bridge-settings select { cursor: pointer; }
|
|
109
|
+
.dsh-miloco-bridge-settings input[type="checkbox"] {
|
|
110
|
+
flex: 0 0 auto; width: auto; min-width: 0; height: auto; margin: 2px 0 0;
|
|
111
|
+
accent-color: var(--mc-accent);
|
|
112
|
+
}
|
|
113
|
+
.dsh-miloco-bridge-settings select:focus, .dsh-miloco-bridge-settings input:focus {
|
|
114
|
+
border-color: var(--mc-accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--mc-accent) 18%, transparent);
|
|
55
115
|
}
|
|
56
116
|
.dsh-miloco-bridge-settings button {
|
|
57
|
-
padding:
|
|
58
|
-
background:
|
|
117
|
+
padding: 6px 12px; border-radius: 7px; border: 1px solid var(--mc-border);
|
|
118
|
+
background: var(--mc-bg-soft); color: inherit; font: inherit; cursor: pointer;
|
|
119
|
+
transition: background .15s ease, border-color .15s ease, opacity .15s ease;
|
|
59
120
|
}
|
|
60
|
-
.dsh-miloco-bridge-settings button
|
|
61
|
-
.dsh-miloco-bridge-settings button
|
|
121
|
+
.dsh-miloco-bridge-settings button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.14)); border-color: var(--mc-text-3); }
|
|
122
|
+
.dsh-miloco-bridge-settings button.primary { background: var(--mc-accent); border-color: transparent; color: #fff; }
|
|
123
|
+
.dsh-miloco-bridge-settings button.primary:hover:not(:disabled) { opacity: .9; }
|
|
124
|
+
.dsh-miloco-bridge-settings button.dangerBtn { color: var(--mc-error); border-color: var(--mc-error); background: transparent; }
|
|
125
|
+
.dsh-miloco-bridge-settings button:disabled { opacity: .45; cursor: default; }
|
|
62
126
|
.dsh-miloco-bridge-settings .pill {
|
|
63
|
-
display: inline-
|
|
64
|
-
border: 1px solid
|
|
127
|
+
display: inline-flex; align-items: center; padding: 1px 8px; border-radius: 999px;
|
|
128
|
+
font-size: 11.5px; line-height: 1.6; border: 1px solid var(--mc-border); color: var(--mc-text-2);
|
|
129
|
+
white-space: nowrap;
|
|
130
|
+
}
|
|
131
|
+
.dsh-miloco-bridge-settings .pill.ok { border-color: var(--mc-success); color: var(--mc-success); }
|
|
132
|
+
.dsh-miloco-bridge-settings .pill.bad { border-color: var(--mc-error); color: var(--mc-error); }
|
|
133
|
+
.dsh-miloco-bridge-settings .pill.warn { border-color: #b8860b; color: #b8860b; }
|
|
134
|
+
.dsh-miloco-bridge-settings .muted { color: var(--mc-text-3); font-size: 12px; }
|
|
135
|
+
.dsh-miloco-bridge-settings .modeList { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 8px; margin: 12px 0 8px; }
|
|
136
|
+
.dsh-miloco-bridge-settings .modeItem {
|
|
137
|
+
display: flex; align-items: flex-start; gap: 9px; margin: 0; padding: 10px 12px;
|
|
138
|
+
border: 1px solid var(--mc-border); border-radius: 9px; background: transparent;
|
|
139
|
+
cursor: pointer; transition: border-color .15s ease, background .15s ease;
|
|
65
140
|
}
|
|
66
|
-
.dsh-miloco-bridge-settings .
|
|
67
|
-
.dsh-miloco-bridge-settings .
|
|
68
|
-
.dsh-miloco-bridge-settings .pill.warn { border-color: rgba(210,150,50,.6); color: rgba(210,150,50,1); }
|
|
69
|
-
.dsh-miloco-bridge-settings .muted { opacity: .68; font-size: 12px; }
|
|
70
|
-
.dsh-miloco-bridge-settings .modeList { display: flex; flex-direction: column; gap: 4px; margin: 10px 0 6px; }
|
|
71
|
-
.dsh-miloco-bridge-settings .modeItem { display: flex; align-items: flex-start; gap: 8px; margin: 0; padding: 6px 8px; border-radius: 6px; }
|
|
72
|
-
.dsh-miloco-bridge-settings .modeItem:hover { background: rgba(128,128,128,.08); }
|
|
141
|
+
.dsh-miloco-bridge-settings .modeItem:hover { border-color: var(--mc-text-3); }
|
|
142
|
+
.dsh-miloco-bridge-settings .modeItem.selected { border-color: var(--mc-accent); background: var(--mc-bg-soft); }
|
|
73
143
|
.dsh-miloco-bridge-settings .modeItem input[type="radio"] {
|
|
74
144
|
flex: 0 0 auto; min-width: 0; width: auto; margin: 2px 0 0; padding: 0;
|
|
75
|
-
border: 0; background: transparent; accent-color:
|
|
145
|
+
border: 0; background: transparent; accent-color: var(--mc-accent);
|
|
76
146
|
}
|
|
77
|
-
.dsh-miloco-bridge-settings .modeText { display: flex; flex: 1 1 auto; flex-direction: column; gap:
|
|
147
|
+
.dsh-miloco-bridge-settings .modeText { display: flex; flex: 1 1 auto; flex-direction: column; gap: 3px; min-width: 0; text-align: left; }
|
|
148
|
+
.dsh-miloco-bridge-settings .modeText strong { font-size: 13px; }
|
|
78
149
|
.dsh-miloco-bridge-settings .modeText .muted { margin: 0; }
|
|
79
|
-
.dsh-miloco-bridge-settings a { color:
|
|
150
|
+
.dsh-miloco-bridge-settings a { color: var(--mc-accent); text-decoration: none; }
|
|
80
151
|
.dsh-miloco-bridge-settings a:hover { text-decoration: underline; }
|
|
81
|
-
.dsh-miloco-bridge-settings .danger { border: 1px solid
|
|
82
|
-
.dsh-miloco-bridge-settings .danger h4 { margin: 0 0 6px; font-size: 13px; color:
|
|
152
|
+
.dsh-miloco-bridge-settings .danger { border: 1px solid var(--mc-error); border-radius: 8px; padding: 10px 12px; margin-top: 10px; background: transparent; }
|
|
153
|
+
.dsh-miloco-bridge-settings .danger h4 { margin: 0 0 6px; font-size: 13px; color: var(--mc-error); }
|
|
83
154
|
.dsh-miloco-bridge-settings .danger label { display: flex; gap: 6px; align-items: flex-start; margin: 6px 0; font-size: 12px; }
|
|
84
|
-
.dsh-miloco-bridge-settings .guide { margin: 8px 0 0; padding: 10px 12px; border-radius:
|
|
155
|
+
.dsh-miloco-bridge-settings .guide { margin: 8px 0 0; padding: 10px 12px; border-radius: 7px; border: 1px solid var(--mc-border-soft); background: transparent; }
|
|
85
156
|
.dsh-miloco-bridge-settings .guide h4 { margin: 8px 0 4px; font-size: 13px; }
|
|
86
157
|
.dsh-miloco-bridge-settings .guide ul { margin: 4px 0; padding-left: 18px; }
|
|
87
158
|
.dsh-miloco-bridge-settings .guide li { margin: 2px 0; }
|
|
159
|
+
.dsh-miloco-bridge-settings .versionSub {
|
|
160
|
+
display: flex; flex-wrap: wrap; align-items: center; flex-basis: 100%;
|
|
161
|
+
min-width: 0; color: var(--mc-text-3); font-size: 12px; line-height: 1.6;
|
|
162
|
+
}
|
|
163
|
+
.dsh-miloco-bridge-settings .versionSub > * + *::before {
|
|
164
|
+
content: '·'; margin: 0 6px; color: var(--mc-text-3);
|
|
165
|
+
}
|
|
166
|
+
.dsh-miloco-bridge-settings .versionSub .versionLink {
|
|
167
|
+
border: 0; background: transparent; padding: 0; border-radius: 0;
|
|
168
|
+
color: var(--mc-accent); font: inherit; line-height: inherit; cursor: pointer;
|
|
169
|
+
}
|
|
170
|
+
.dsh-miloco-bridge-settings .versionSub .versionLink:hover:not(:disabled) {
|
|
171
|
+
background: transparent; text-decoration: underline;
|
|
172
|
+
}
|
|
173
|
+
.dsh-miloco-bridge-settings .versionDirty { color: #b8860b; }
|
|
174
|
+
.dsh-miloco-bridge-settings .versionNote { margin-top: 6px; }
|
|
175
|
+
.dsh-miloco-bridge-settings .versionManualPanel { margin-top: 8px; }
|
|
176
|
+
.dsh-miloco-bridge-settings .versionCommand {
|
|
177
|
+
margin: 0; padding: 8px 10px; border-radius: 6px; max-height: 120px; overflow: auto;
|
|
178
|
+
border: 1px solid var(--mc-border-soft); background: var(--dsw-alias-bg-base, transparent);
|
|
179
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
180
|
+
font-size: 11.5px; line-height: 1.5; white-space: pre-wrap; word-break: break-word;
|
|
181
|
+
}
|
|
88
182
|
.dsh-miloco-bridge-settings code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
|
|
183
|
+
.dsh-miloco-preset-helpbar { display: inline-flex; align-items: center; gap: 2px; margin-right: auto; }
|
|
184
|
+
.dsh-miloco-preset-helpbtn {
|
|
185
|
+
border: 0; background: transparent; color: var(--dsw-alias-label-secondary, rgba(128,128,128,.95));
|
|
186
|
+
font: inherit; font-size: 12px; line-height: 1.5; padding: 2px 6px; border-radius: 5px; cursor: pointer;
|
|
187
|
+
}
|
|
188
|
+
.dsh-miloco-preset-helpbtn:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
189
|
+
.dsh-miloco-guide-backdrop {
|
|
190
|
+
position: fixed; inset: 0; z-index: 10000; display: flex; align-items: center; justify-content: center;
|
|
191
|
+
padding: 24px; background: rgba(0,0,0,.24);
|
|
192
|
+
}
|
|
193
|
+
.dsh-miloco-guide-modal {
|
|
194
|
+
display: flex; flex-direction: column; width: min(640px, 100%); height: min(600px, 100%);
|
|
195
|
+
max-height: 100%; overflow: hidden; padding: 0; border-radius: 16px; border: 0;
|
|
196
|
+
background: var(--dsw-alias-bg-base, #fff); color: var(--dsw-alias-label-primary, #111);
|
|
197
|
+
box-shadow: 0 18px 56px rgba(0,0,0,.22);
|
|
198
|
+
}
|
|
199
|
+
.dsh-miloco-guide-header { flex: none; padding: 24px 24px 20px 28px; }
|
|
200
|
+
.dsh-miloco-guide-title-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
|
|
201
|
+
.dsh-miloco-guide-title { margin: 0; font-size: 20px; font-weight: 600; line-height: 28px; }
|
|
202
|
+
.dsh-miloco-guide-intro { margin: 8px 0 0; color: var(--dsw-alias-label-secondary, rgba(128,128,128,.95)); font-size: 14px; line-height: 22px; }
|
|
203
|
+
.dsh-miloco-guide-close {
|
|
204
|
+
flex: none; width: 32px; height: 32px; border: 0; border-radius: 8px; padding: 0;
|
|
205
|
+
background: transparent; color: var(--dsw-alias-label-tertiary, rgba(128,128,128,.8));
|
|
206
|
+
font: inherit; font-size: 22px; line-height: 1; cursor: pointer;
|
|
207
|
+
}
|
|
208
|
+
.dsh-miloco-guide-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
209
|
+
.dsh-miloco-guide-tabs {
|
|
210
|
+
flex: none; display: grid; grid-template-columns: 1fr 1fr; gap: 4px;
|
|
211
|
+
margin: 0 28px 22px; padding: 4px; border-radius: 10px;
|
|
212
|
+
background: var(--dsw-alias-bg-l2, #f5f5f5);
|
|
213
|
+
}
|
|
214
|
+
.dsh-miloco-guide-tab {
|
|
215
|
+
border: 1px solid transparent; border-radius: 8px; padding: 7px 10px;
|
|
216
|
+
background: transparent; color: var(--dsw-alias-label-secondary, rgba(128,128,128,.95));
|
|
217
|
+
font: inherit; font-size: 14px; line-height: 20px; cursor: pointer;
|
|
218
|
+
}
|
|
219
|
+
.dsh-miloco-guide-tab.selected {
|
|
220
|
+
background: var(--dsw-alias-bg-base, #fff);
|
|
221
|
+
border-color: var(--dsw-alias-border-l2, rgba(128,128,128,.24));
|
|
222
|
+
color: var(--dsw-alias-label-primary, #111);
|
|
223
|
+
box-shadow: 0 1px 2px rgba(0,0,0,.04);
|
|
224
|
+
}
|
|
225
|
+
.dsh-miloco-guide-panel {
|
|
226
|
+
flex: 1; min-height: 0; overflow-y: auto; padding: 0 24px 24px 28px;
|
|
227
|
+
color: var(--dsw-alias-label-primary, #111); font-size: 16px; overflow-wrap: anywhere;
|
|
228
|
+
}
|
|
229
|
+
.dsh-miloco-guide-panel h3 { margin: 28px 0 10px; font-size: 16px; font-weight: 600; line-height: 24px; }
|
|
230
|
+
.dsh-miloco-guide-panel h3:first-child { margin-top: 0; }
|
|
231
|
+
.dsh-miloco-guide-panel p { margin: 0 0 16px; font-size: 16px; line-height: 1.625; }
|
|
232
|
+
.dsh-miloco-guide-panel ul { margin: 0 0 16px; padding-left: 22px; }
|
|
233
|
+
.dsh-miloco-guide-panel li { margin: 6px 0; font-size: 16px; line-height: 1.6; }
|
|
234
|
+
.dsh-miloco-guide-panel blockquote { margin: 10px 0 14px; padding: 12px 16px; border: 0; border-radius: 10px; background: var(--dsw-alias-bg-module-platform, var(--dsw-alias-bg-l2, #f5f5f5)); }
|
|
235
|
+
.dsh-miloco-guide-panel blockquote p { margin: 0; }
|
|
89
236
|
.dsh-miloco-bridge-settings pre {
|
|
90
237
|
margin: 8px 0 0; padding: 8px 10px; border-radius: 6px; max-height: 260px; overflow: auto;
|
|
91
|
-
border: 1px solid
|
|
238
|
+
border: 1px solid var(--mc-border-soft); font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
92
239
|
font-size: 11.5px; line-height: 1.5; white-space: pre-wrap;
|
|
93
240
|
}
|
|
94
241
|
`
|
|
@@ -169,6 +316,157 @@ window.__ModuleLoader__.load({
|
|
|
169
316
|
return options
|
|
170
317
|
}
|
|
171
318
|
|
|
319
|
+
/**
|
|
320
|
+
* DSH 0.2.1 only ships guide dialogs for built-in presets. Add equivalent
|
|
321
|
+
* explanation/usage buttons to the custom Miloco preset card so the same
|
|
322
|
+
* help is reachable from 「设置 → Agent 预设」. This is a defensive DOM
|
|
323
|
+
* enhancement: if DSH changes the card structure, it silently does nothing.
|
|
324
|
+
*/
|
|
325
|
+
const PRESET_GUIDE = {
|
|
326
|
+
title: 'Miloco 家庭助手',
|
|
327
|
+
intro: {
|
|
328
|
+
explanation: '只读和管家两种模式决定这个预设能做什么。',
|
|
329
|
+
usage: '新建任务时选择「Miloco 家庭助手」,说明要完成什么、涉及哪个房间或家人。',
|
|
330
|
+
},
|
|
331
|
+
content: {
|
|
332
|
+
explanation: [
|
|
333
|
+
'<h3>保留哪些能力</h3>',
|
|
334
|
+
'<p>保留 DSH 通用 Agent 的对话与工具调用;米家设备、摄像头、感知、规则/任务与家庭档案',
|
|
335
|
+
'是它独有的能力,且只在这个预设内出现。</p>',
|
|
336
|
+
'<h3>什么时候选</h3>',
|
|
337
|
+
'<p>想要联动家居设备时:只需要了解家里情况时选只读;需要操作设备或维护家庭信息时选管家。',
|
|
338
|
+
'涉及门锁、燃气、摄像头开关等动作时,技能层会要求二次确认。</p>',
|
|
339
|
+
'<p>模式说明与示例见「设置 → 插件 → Miloco」。</p>',
|
|
340
|
+
].join(''),
|
|
341
|
+
usage: [
|
|
342
|
+
'<h3>查看家里情况</h3>',
|
|
343
|
+
'<blockquote><p>客厅现在有人吗?刚才门口发生了什么?</p></blockquote>',
|
|
344
|
+
'<p>用摄像头画面和感知事件回答,不控制设备。</p>',
|
|
345
|
+
'<h3>操作设备或执行场景</h3>',
|
|
346
|
+
'<blockquote><p>把客厅灯打开;执行回家模式。</p></blockquote>',
|
|
347
|
+
'<p>需要管家模式;控制前会先确认设备与参数。</p>',
|
|
348
|
+
'<h3>维护家庭信息</h3>',
|
|
349
|
+
'<blockquote><p>记一下孩子每天喝几杯水;奶奶对什么过敏?</p></blockquote>',
|
|
350
|
+
'<p>用于家庭档案、成员与宠物登记,以及任务创建。</p>',
|
|
351
|
+
].join(''),
|
|
352
|
+
},
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Open a read-only help dialog matching DSH's native preset guide layout.
|
|
357
|
+
* @param {'explanation'|'usage'} kind - Initial guide page.
|
|
358
|
+
*/
|
|
359
|
+
function openPresetGuide(kind) {
|
|
360
|
+
if (typeof document === 'undefined' || !document.body) return
|
|
361
|
+
document.querySelector('.dsh-miloco-guide-backdrop')?.remove()
|
|
362
|
+
const backdrop = document.createElement('div')
|
|
363
|
+
backdrop.className = 'dsh-miloco-guide-backdrop'
|
|
364
|
+
const modal = document.createElement('div')
|
|
365
|
+
modal.className = 'dsh-miloco-guide-modal'
|
|
366
|
+
modal.setAttribute('role', 'dialog')
|
|
367
|
+
modal.setAttribute('aria-modal', 'true')
|
|
368
|
+
|
|
369
|
+
const header = document.createElement('div')
|
|
370
|
+
header.className = 'dsh-miloco-guide-header'
|
|
371
|
+
const titleRow = document.createElement('div')
|
|
372
|
+
titleRow.className = 'dsh-miloco-guide-title-row'
|
|
373
|
+
const title = document.createElement('h2')
|
|
374
|
+
title.className = 'dsh-miloco-guide-title'
|
|
375
|
+
title.textContent = PRESET_GUIDE.title
|
|
376
|
+
const close = document.createElement('button')
|
|
377
|
+
close.type = 'button'
|
|
378
|
+
close.className = 'dsh-miloco-guide-close'
|
|
379
|
+
close.textContent = '×'
|
|
380
|
+
close.setAttribute('aria-label', '关闭')
|
|
381
|
+
const intro = document.createElement('p')
|
|
382
|
+
intro.className = 'dsh-miloco-guide-intro'
|
|
383
|
+
titleRow.append(title, close)
|
|
384
|
+
header.append(titleRow, intro)
|
|
385
|
+
|
|
386
|
+
const tabs = document.createElement('div')
|
|
387
|
+
tabs.className = 'dsh-miloco-guide-tabs'
|
|
388
|
+
const tabExplanation = document.createElement('button')
|
|
389
|
+
tabExplanation.type = 'button'
|
|
390
|
+
tabExplanation.className = 'dsh-miloco-guide-tab'
|
|
391
|
+
tabExplanation.textContent = '模式说明'
|
|
392
|
+
const tabUsage = document.createElement('button')
|
|
393
|
+
tabUsage.type = 'button'
|
|
394
|
+
tabUsage.className = 'dsh-miloco-guide-tab'
|
|
395
|
+
tabUsage.textContent = '如何使用'
|
|
396
|
+
tabs.append(tabExplanation, tabUsage)
|
|
397
|
+
|
|
398
|
+
const panel = document.createElement('div')
|
|
399
|
+
panel.className = 'dsh-miloco-guide-panel'
|
|
400
|
+
|
|
401
|
+
const setPage = next => {
|
|
402
|
+
const active = next === 'usage' ? 'usage' : 'explanation'
|
|
403
|
+
panel.innerHTML = PRESET_GUIDE.content[active]
|
|
404
|
+
panel.dataset.guidePage = active
|
|
405
|
+
intro.textContent = PRESET_GUIDE.intro[active]
|
|
406
|
+
tabExplanation.classList.toggle('selected', active === 'explanation')
|
|
407
|
+
tabUsage.classList.toggle('selected', active === 'usage')
|
|
408
|
+
}
|
|
409
|
+
tabExplanation.addEventListener('click', () => setPage('explanation'))
|
|
410
|
+
tabUsage.addEventListener('click', () => setPage('usage'))
|
|
411
|
+
setPage(kind)
|
|
412
|
+
|
|
413
|
+
modal.append(header, tabs, panel)
|
|
414
|
+
backdrop.append(modal)
|
|
415
|
+
|
|
416
|
+
const dismiss = () => {
|
|
417
|
+
backdrop.remove()
|
|
418
|
+
document.removeEventListener('keydown', onKey)
|
|
419
|
+
}
|
|
420
|
+
const onKey = event => {
|
|
421
|
+
if (event.key === 'Escape') dismiss()
|
|
422
|
+
}
|
|
423
|
+
close.addEventListener('click', dismiss)
|
|
424
|
+
backdrop.addEventListener('click', event => {
|
|
425
|
+
if (event.target === backdrop) dismiss()
|
|
426
|
+
})
|
|
427
|
+
document.addEventListener('keydown', onKey)
|
|
428
|
+
document.body.append(backdrop)
|
|
429
|
+
close.focus()
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Add help buttons to the custom Miloco preset card when it appears.
|
|
434
|
+
* @param {any} ctx - Browser plugin context.
|
|
435
|
+
*/
|
|
436
|
+
function installPresetGuide(ctx) {
|
|
437
|
+
if (typeof document === 'undefined' || !document.body) return
|
|
438
|
+
const enhance = () => {
|
|
439
|
+
const card = document.querySelector('[data-agent-preset-id="miloco"]')
|
|
440
|
+
if (!card || card.dataset.dshMilocoGuide === '1') return
|
|
441
|
+
const foot = card.querySelector('[class*="cardFoot"]')
|
|
442
|
+
if (!foot || foot.querySelector('.dsh-miloco-preset-helpbar')) return
|
|
443
|
+
const bar = document.createElement('span')
|
|
444
|
+
bar.className = 'dsh-miloco-preset-helpbar'
|
|
445
|
+
const makeButton = (label, kind) => {
|
|
446
|
+
const button = document.createElement('button')
|
|
447
|
+
button.type = 'button'
|
|
448
|
+
button.className = 'dsh-miloco-preset-helpbtn'
|
|
449
|
+
button.textContent = label
|
|
450
|
+
button.setAttribute('aria-label', `${label}: Miloco 家庭助手`)
|
|
451
|
+
button.addEventListener('click', event => {
|
|
452
|
+
event.preventDefault()
|
|
453
|
+
event.stopPropagation()
|
|
454
|
+
openPresetGuide(kind)
|
|
455
|
+
})
|
|
456
|
+
return button
|
|
457
|
+
}
|
|
458
|
+
bar.append(makeButton('模式说明', 'explanation'), makeButton('如何使用', 'usage'))
|
|
459
|
+
foot.insertBefore(bar, foot.firstChild)
|
|
460
|
+
card.dataset.dshMilocoGuide = '1'
|
|
461
|
+
}
|
|
462
|
+
const observer = new MutationObserver(enhance)
|
|
463
|
+
observer.observe(document.body, { childList: true, subtree: true })
|
|
464
|
+
enhance()
|
|
465
|
+
try {
|
|
466
|
+
ctx.effect?.(() => () => observer.disconnect(), 'dsh-miloco: preset guide')
|
|
467
|
+
} catch { /* 客户端页面卸载时忽略 */ }
|
|
468
|
+
}
|
|
469
|
+
|
|
172
470
|
function MilocoSettingsTab() {
|
|
173
471
|
const [status, setStatus] = React.useState(null)
|
|
174
472
|
const [error, setError] = React.useState(null)
|
|
@@ -191,6 +489,14 @@ window.__ModuleLoader__.load({
|
|
|
191
489
|
const [confirmText, setConfirmText] = React.useState('')
|
|
192
490
|
const [uninstallBusy, setUninstallBusy] = React.useState(false)
|
|
193
491
|
const [uninstallResult, setUninstallResult] = React.useState(null)
|
|
492
|
+
const [versionInfo, setVersionInfo] = React.useState(null)
|
|
493
|
+
const [versionBusy, setVersionBusy] = React.useState(false)
|
|
494
|
+
const [versionError, setVersionError] = React.useState(null)
|
|
495
|
+
const [versionCommandOpen, setVersionCommandOpen] = React.useState(false)
|
|
496
|
+
const [upgradeStatus, setUpgradeStatus] = React.useState(null)
|
|
497
|
+
const [upgradeConfirmOpen, setUpgradeConfirmOpen] = React.useState(false)
|
|
498
|
+
const [upgradeBusy, setUpgradeBusy] = React.useState(false)
|
|
499
|
+
const [upgradePolling, setUpgradePolling] = React.useState(false)
|
|
194
500
|
|
|
195
501
|
const reload = React.useCallback(() => {
|
|
196
502
|
setError(null)
|
|
@@ -209,7 +515,74 @@ window.__ModuleLoader__.load({
|
|
|
209
515
|
.catch((cause) => setError(cause instanceof Error ? cause.message : String(cause)))
|
|
210
516
|
}, [])
|
|
211
517
|
|
|
212
|
-
React.
|
|
518
|
+
const loadVersion = React.useCallback((refresh = false) => {
|
|
519
|
+
setVersionBusy(true)
|
|
520
|
+
setVersionError(null)
|
|
521
|
+
call(`/version${refresh ? '?refresh=1' : ''}`)
|
|
522
|
+
.then((payload) => {
|
|
523
|
+
setVersionInfo(payload)
|
|
524
|
+
setUpgradeStatus(payload.upgradeStatus ?? null)
|
|
525
|
+
if (payload.error) setVersionError(payload.error)
|
|
526
|
+
})
|
|
527
|
+
.catch((cause) => setVersionError(cause instanceof Error ? cause.message : String(cause)))
|
|
528
|
+
.finally(() => setVersionBusy(false))
|
|
529
|
+
}, [])
|
|
530
|
+
|
|
531
|
+
const pollUpgrade = React.useCallback(() => {
|
|
532
|
+
setUpgradePolling(true)
|
|
533
|
+
const started = Date.now()
|
|
534
|
+
const tick = () => {
|
|
535
|
+
call('/upgrade/status')
|
|
536
|
+
.then((payload) => {
|
|
537
|
+
const status = payload.status
|
|
538
|
+
setUpgradeStatus(status)
|
|
539
|
+
if (status && ['done', 'failed', 'idle'].includes(status.phase)) {
|
|
540
|
+
setUpgradePolling(false)
|
|
541
|
+
loadVersion(true)
|
|
542
|
+
return
|
|
543
|
+
}
|
|
544
|
+
if (Date.now() - started > 5 * 60_000) {
|
|
545
|
+
setUpgradePolling(false)
|
|
546
|
+
loadVersion(true)
|
|
547
|
+
return
|
|
548
|
+
}
|
|
549
|
+
window.setTimeout(tick, 3000)
|
|
550
|
+
})
|
|
551
|
+
.catch(() => {
|
|
552
|
+
// The upgrade restarts the backend; treat this as the restart phase.
|
|
553
|
+
setUpgradeStatus({ phase: 'restarting' })
|
|
554
|
+
if (Date.now() - started > 5 * 60_000) {
|
|
555
|
+
setUpgradePolling(false)
|
|
556
|
+
loadVersion(true)
|
|
557
|
+
return
|
|
558
|
+
}
|
|
559
|
+
window.setTimeout(tick, 3000)
|
|
560
|
+
})
|
|
561
|
+
}
|
|
562
|
+
tick()
|
|
563
|
+
}, [loadVersion])
|
|
564
|
+
|
|
565
|
+
const runUpgrade = React.useCallback(() => {
|
|
566
|
+
setUpgradeBusy(true)
|
|
567
|
+
setVersionError(null)
|
|
568
|
+
call('/upgrade', {
|
|
569
|
+
method: 'POST',
|
|
570
|
+
headers: { 'content-type': 'application/json' },
|
|
571
|
+
body: JSON.stringify({}),
|
|
572
|
+
})
|
|
573
|
+
.then(() => {
|
|
574
|
+
setUpgradeConfirmOpen(false)
|
|
575
|
+
setUpgradeStatus({ phase: 'starting' })
|
|
576
|
+
pollUpgrade()
|
|
577
|
+
})
|
|
578
|
+
.catch((cause) => setVersionError(cause instanceof Error ? cause.message : String(cause)))
|
|
579
|
+
.finally(() => setUpgradeBusy(false))
|
|
580
|
+
}, [pollUpgrade])
|
|
581
|
+
|
|
582
|
+
React.useEffect(() => {
|
|
583
|
+
reload()
|
|
584
|
+
loadVersion(false)
|
|
585
|
+
}, [reload, loadVersion])
|
|
213
586
|
|
|
214
587
|
const refreshSessions = React.useCallback(() => {
|
|
215
588
|
call('/sessions')
|
|
@@ -371,7 +744,8 @@ window.__ModuleLoader__.load({
|
|
|
371
744
|
onChange: (event) => setConnectPushToken(event.target.value),
|
|
372
745
|
})),
|
|
373
746
|
h('div', { className: 'row' },
|
|
374
|
-
h('button', { type: 'button',
|
|
747
|
+
h('button', { type: 'button', onClick: () => setConnectOpen(false) }, '取消'),
|
|
748
|
+
h('button', { type: 'button', className: 'primary', disabled: busy || !connectUrl, onClick: connect }, '连接')))
|
|
375
749
|
: null
|
|
376
750
|
|
|
377
751
|
// ---------------------------------------------------------------- 部署 / 连接
|
|
@@ -381,11 +755,20 @@ window.__ModuleLoader__.load({
|
|
|
381
755
|
h('div', { className: 'muted' },
|
|
382
756
|
backend.error || `连不上 ${connection.baseUrl || '(未配置地址)'}。`
|
|
383
757
|
+ '插件本身可以独立安装;部署或连接之后才会出现设备与感知能力。'),
|
|
758
|
+
h('div', { className: 'muted' },
|
|
759
|
+
'一键部署会下载官方 release 安装器(支持 GitHub 镜像),不会在本机编译源码。'),
|
|
384
760
|
h('div', { className: 'row' },
|
|
385
761
|
h('button', { type: 'button', className: 'primary', disabled: busy, onClick: deploy },
|
|
386
762
|
busy ? '部署中…' : '一键部署 Miloco'),
|
|
387
|
-
h('button', {
|
|
388
|
-
|
|
763
|
+
h('button', {
|
|
764
|
+
type: 'button',
|
|
765
|
+
disabled: busy,
|
|
766
|
+
onClick: () => {
|
|
767
|
+
const opening = !connectOpen
|
|
768
|
+
setConnectOpen(opening)
|
|
769
|
+
if (opening) setUninstallOpen(false)
|
|
770
|
+
},
|
|
771
|
+
}, '连接已有的 Miloco')),
|
|
389
772
|
connectForm,
|
|
390
773
|
deployLog ? h('pre', null, deployLog) : null))
|
|
391
774
|
}
|
|
@@ -397,7 +780,7 @@ window.__ModuleLoader__.load({
|
|
|
397
780
|
h('div', { className: 'grid' },
|
|
398
781
|
h('div', { className: 'stat' },
|
|
399
782
|
h('span', { className: 'k' }, '米家账号'),
|
|
400
|
-
h('span', { className: 'v' }, overview.account?.nickname || (overview.accountBound ? '已绑定' : '未绑定'))),
|
|
783
|
+
h('span', { className: 'v', title: overview.account?.nickname || '' }, overview.account?.nickname || (overview.accountBound ? '已绑定' : '未绑定'))),
|
|
401
784
|
h('div', { className: 'stat' },
|
|
402
785
|
h('span', { className: 'k' }, '设备'),
|
|
403
786
|
h('span', { className: 'v' }, overview.deviceCount ?? '—')),
|
|
@@ -430,7 +813,7 @@ window.__ModuleLoader__.load({
|
|
|
430
813
|
h('div', { className: 'muted' }, '模式决定这个预设手里有哪些工具:'),
|
|
431
814
|
h('div', { className: 'modeList' }, modes.map(item => h('label', {
|
|
432
815
|
key: item.id,
|
|
433
|
-
className:
|
|
816
|
+
className: `modeItem${mode === item.id ? ' selected' : ''}`,
|
|
434
817
|
},
|
|
435
818
|
h('input', {
|
|
436
819
|
type: 'radio', name: 'dsh-miloco-bridge-mode', checked: mode === item.id,
|
|
@@ -478,60 +861,62 @@ window.__ModuleLoader__.load({
|
|
|
478
861
|
: ''
|
|
479
862
|
cards.push(h('div', { className: 'card', key: 'target' },
|
|
480
863
|
h('h3', null, '推送目标'),
|
|
481
|
-
h('div', { className: '
|
|
864
|
+
h('div', { className: 'field' },
|
|
482
865
|
h('label', null, '对话会话'),
|
|
483
|
-
h('
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
866
|
+
h('div', { className: 'fieldRow' },
|
|
867
|
+
h('input', {
|
|
868
|
+
type: 'text',
|
|
869
|
+
list: SESSION_LIST_ID,
|
|
870
|
+
value: sessionId,
|
|
871
|
+
placeholder: lastSession
|
|
872
|
+
? `留空 = 上一次触发 miloco 的会话(${lastSession})`
|
|
873
|
+
: '留空 = 上一次触发 miloco 的会话(还没有)',
|
|
874
|
+
onChange: (event) => setSessionId(event.target.value),
|
|
875
|
+
}),
|
|
876
|
+
h('datalist', { id: SESSION_LIST_ID }, sessionOptions(status)),
|
|
877
|
+
h('button', { type: 'button', disabled: busy, onClick: refreshSessions }, '刷新会话'))),
|
|
494
878
|
h('div', { className: 'muted' },
|
|
495
879
|
'留空 = 投到「上一次触发 miloco 的会话」,也就是最近一次调用过米家工具的对话。'
|
|
496
880
|
+ '也可以从下拉里选任意对话,或直接填会话 id。'),
|
|
497
|
-
h('div', { className: '
|
|
498
|
-
h('
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
h('
|
|
881
|
+
h('div', { className: 'formGrid' },
|
|
882
|
+
h('div', { className: 'field' },
|
|
883
|
+
h('label', null, '模型'),
|
|
884
|
+
h('select', {
|
|
885
|
+
value: modelValue,
|
|
886
|
+
onChange: (event) => {
|
|
887
|
+
setModelValue(event.target.value)
|
|
888
|
+
setReasoningEffort('')
|
|
889
|
+
},
|
|
890
|
+
}, modelOptions(status))),
|
|
891
|
+
selectedEfforts.length > 0
|
|
892
|
+
? h('div', { className: 'field' },
|
|
893
|
+
h('label', null, '推理强度'),
|
|
894
|
+
h('select', {
|
|
895
|
+
value: effortValue,
|
|
896
|
+
onChange: (event) => setReasoningEffort(event.target.value),
|
|
897
|
+
}, [
|
|
898
|
+
h('option', { key: '__model_default__', value: '' }, '跟随模型默认'),
|
|
899
|
+
...selectedEfforts.map(item => h('option', {
|
|
900
|
+
key: item.id,
|
|
901
|
+
value: item.id,
|
|
902
|
+
}, item.name || item.id)),
|
|
903
|
+
]))
|
|
904
|
+
: null,
|
|
905
|
+
h('div', { className: 'field' },
|
|
906
|
+
h('label', null, '感知等级'),
|
|
509
907
|
h('select', {
|
|
510
|
-
value:
|
|
511
|
-
onChange: (event) =>
|
|
908
|
+
value: perceptionLevel,
|
|
909
|
+
onChange: (event) => setPerceptionLevel(event.target.value),
|
|
512
910
|
}, [
|
|
513
|
-
h('option', { key: '
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
]))
|
|
519
|
-
: null,
|
|
520
|
-
h('div', { className: 'row' },
|
|
521
|
-
h('label', null, '感知等级'),
|
|
522
|
-
h('select', {
|
|
523
|
-
value: perceptionLevel,
|
|
524
|
-
onChange: (event) => setPerceptionLevel(event.target.value),
|
|
525
|
-
}, [
|
|
526
|
-
h('option', { key: 'all', value: 'all' }, '全部事件'),
|
|
527
|
-
h('option', { key: 'low', value: 'low' }, '低及以上'),
|
|
528
|
-
h('option', { key: 'medium', value: 'medium' }, '中及以上'),
|
|
529
|
-
h('option', { key: 'high', value: 'high' }, '仅高优先级'),
|
|
530
|
-
])),
|
|
911
|
+
h('option', { key: 'all', value: 'all' }, '全部事件'),
|
|
912
|
+
h('option', { key: 'low', value: 'low' }, '低及以上'),
|
|
913
|
+
h('option', { key: 'medium', value: 'medium' }, '中及以上'),
|
|
914
|
+
h('option', { key: 'high', value: 'high' }, '仅高优先级'),
|
|
915
|
+
]))),
|
|
531
916
|
h('div', { className: 'muted' },
|
|
532
917
|
'低于所选等级的感知建议不会投递到 DSH;规则、任务、onboarding 与普通对话事件不受影响。'),
|
|
533
918
|
h('div', { className: 'row' },
|
|
534
|
-
h('button', { type: 'button', disabled: busy, onClick: save }, busy ? '保存中…' : '保存'),
|
|
919
|
+
h('button', { type: 'button', className: 'primary', disabled: busy, onClick: save }, busy ? '保存中…' : '保存'),
|
|
535
920
|
saved ? h('span', { className: 'pill ok' }, saved) : null,
|
|
536
921
|
error ? h('span', { className: 'pill bad' }, error) : null),
|
|
537
922
|
h('div', { className: 'muted' },
|
|
@@ -539,47 +924,231 @@ window.__ModuleLoader__.load({
|
|
|
539
924
|
|
|
540
925
|
// ---------------------------------------------------------------- 运行状态
|
|
541
926
|
const deployState = status.deploy ?? {}
|
|
927
|
+
const versionData = versionInfo?.version ?? null
|
|
928
|
+
const upgradeInfo = versionInfo?.upgrade ?? null
|
|
929
|
+
const currentVersion = upgradeInfo?.current || versionData?.version || ''
|
|
930
|
+
const latestVersion = upgradeInfo?.latest || ''
|
|
931
|
+
const deployKind = upgradeInfo?.deploy_kind
|
|
932
|
+
|| (currentVersion && /\.dev\d*|\+/.test(currentVersion) ? 'dev' : '')
|
|
933
|
+
const hasUpdate = Boolean(upgradeInfo?.has_update)
|
|
934
|
+
const isDevDeployment = deployKind === 'dev'
|
|
935
|
+
const upgradeSource = upgradeInfo?.source || 'backend'
|
|
936
|
+
const mirrorCheck = upgradeSource === 'mirror'
|
|
937
|
+
const upgradeUnavailable = !upgradeInfo || upgradeInfo.reachable === false
|
|
938
|
+
const releaseUrl = upgradeInfo?.release_url || ''
|
|
939
|
+
const sourceRoot = versionInfo?.sourceRoot || deployState.sourceDir || ''
|
|
940
|
+
const gitDirty = Boolean(versionData?.git?.dirty)
|
|
941
|
+
const commitShort = versionData?.git?.commit_short || ''
|
|
942
|
+
const upgradePhase = upgradeStatus?.phase || ''
|
|
943
|
+
const upgradeLogPath = upgradeStatus?.logPath || ''
|
|
944
|
+
const upgradePhaseLabels = {
|
|
945
|
+
idle: '空闲',
|
|
946
|
+
starting: '准备中',
|
|
947
|
+
downloading: '下载中',
|
|
948
|
+
installing: '安装中',
|
|
949
|
+
restarting: '服务重启中',
|
|
950
|
+
done: '已完成',
|
|
951
|
+
failed: '失败',
|
|
952
|
+
}
|
|
953
|
+
const devUpdateCommand = sourceRoot
|
|
954
|
+
? `cd ${sourceRoot} && git pull --ff-only && bash scripts/install.sh --dev --skip-openclaw && systemctl --user restart miloco.service`
|
|
955
|
+
: ''
|
|
956
|
+
const versionPill = versionInfo?.reachable === false
|
|
957
|
+
? ['bad', '不可用']
|
|
958
|
+
: !versionInfo
|
|
959
|
+
? ['', versionBusy ? '读取中' : '未知']
|
|
960
|
+
: isDevDeployment
|
|
961
|
+
? ['', '源码部署']
|
|
962
|
+
: upgradeUnavailable
|
|
963
|
+
? ['warn', '检查失败']
|
|
964
|
+
: hasUpdate
|
|
965
|
+
? ['warn', '有新版本']
|
|
966
|
+
: ['ok', '已是最新']
|
|
967
|
+
|
|
542
968
|
cards.push(h('div', { className: 'card', key: 'runtime' },
|
|
543
|
-
h('
|
|
544
|
-
|
|
545
|
-
h('
|
|
546
|
-
|
|
547
|
-
?
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
h('
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
h('
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
969
|
+
h('div', { className: 'cardHead' },
|
|
970
|
+
h('h3', null, '运行状态'),
|
|
971
|
+
h('button', {
|
|
972
|
+
type: 'button',
|
|
973
|
+
className: `iconBtn${busy || versionBusy ? ' spinning' : ''}`,
|
|
974
|
+
title: '刷新运行状态与版本信息',
|
|
975
|
+
'aria-label': '刷新运行状态与版本信息',
|
|
976
|
+
disabled: busy,
|
|
977
|
+
onClick: () => {
|
|
978
|
+
reload()
|
|
979
|
+
loadVersion(true)
|
|
980
|
+
},
|
|
981
|
+
}, h('svg', {
|
|
982
|
+
viewBox: '0 0 24 24',
|
|
983
|
+
width: 16,
|
|
984
|
+
height: 16,
|
|
985
|
+
fill: 'none',
|
|
986
|
+
stroke: 'currentColor',
|
|
987
|
+
strokeWidth: 1.8,
|
|
988
|
+
strokeLinecap: 'round',
|
|
989
|
+
strokeLinejoin: 'round',
|
|
990
|
+
'aria-hidden': 'true',
|
|
991
|
+
},
|
|
992
|
+
h('path', { d: 'M20 11a8.1 8.1 0 0 0-15.5-2m-.5-4v4h4' }),
|
|
993
|
+
h('path', { d: 'M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4' })))),
|
|
994
|
+
h('div', { className: 'statusRow' },
|
|
995
|
+
h('span', { className: 'statusLabel' }, '后端'),
|
|
996
|
+
h('div', { className: 'statusValue' },
|
|
997
|
+
connection.baseUrl
|
|
998
|
+
? h('a', {
|
|
999
|
+
href: connection.baseUrl,
|
|
1000
|
+
target: '_blank',
|
|
1001
|
+
rel: 'noopener noreferrer',
|
|
1002
|
+
title: '在新标签页打开 Miloco 面板',
|
|
1003
|
+
}, connection.baseUrl)
|
|
1004
|
+
: h('code', null, '—'),
|
|
1005
|
+
h('span', { className: 'muted' }, `来源:${connection.source ?? '—'}`)),
|
|
1006
|
+
reachable
|
|
1007
|
+
? h('span', { className: 'statusCheck', title: '已连通', 'aria-label': '已连通' }, '✓')
|
|
1008
|
+
: h('span', { className: 'statusEnd pill bad' }, '连不上')),
|
|
1009
|
+
h('div', { className: 'statusRow' },
|
|
1010
|
+
h('span', { className: 'statusLabel' }, '推送入口'),
|
|
1011
|
+
h('div', { className: 'statusValue' },
|
|
1012
|
+
h('code', null, push.path || '—')),
|
|
1013
|
+
push.tokenSet
|
|
1014
|
+
? h('span', { className: 'statusCheck', title: '令牌已配置', 'aria-label': '令牌已配置' }, '✓')
|
|
1015
|
+
: h('span', { className: 'statusEnd pill bad' }, '未配置')),
|
|
1016
|
+
h('div', { className: 'statusRow' },
|
|
1017
|
+
h('span', { className: 'statusLabel' }, 'Miloco adapter'),
|
|
1018
|
+
h('div', { className: 'statusValue' },
|
|
1019
|
+
h('span', { className: 'muted' },
|
|
1020
|
+
adapter.installed
|
|
1021
|
+
? `agent.platform=${adapter.agentPlatform || '未设置'}`
|
|
1022
|
+
: '安装 Miloco 后可一键写入 adapter 与令牌')),
|
|
1023
|
+
adapter.active
|
|
1024
|
+
? h('span', { className: 'statusCheck', title: '已启用', 'aria-label': '已启用' }, '✓')
|
|
1025
|
+
: h('span', { className: `statusEnd pill ${adapter.installed ? 'warn' : 'bad'}` },
|
|
1026
|
+
adapter.installed ? '未启用' : '未安装')),
|
|
1027
|
+
h('div', { className: 'statusRow' },
|
|
1028
|
+
h('span', { className: 'statusLabel' }, 'Miloco 版本'),
|
|
1029
|
+
h('div', { className: 'statusValue' },
|
|
1030
|
+
h('code', {
|
|
1031
|
+
title: currentVersion || '未知',
|
|
1032
|
+
}, currentVersion ? currentVersion.split('+')[0] : '—'),
|
|
1033
|
+
commitShort ? h('span', { className: 'muted' }, commitShort) : null,
|
|
1034
|
+
h('span', { className: `pill ${versionPill[0]}` }, versionPill[1]),
|
|
1035
|
+
h('div', { className: 'versionSub' },
|
|
1036
|
+
latestVersion
|
|
1037
|
+
? h('span', null, `最新正式版 ${latestVersion}`)
|
|
1038
|
+
: null,
|
|
1039
|
+
mirrorCheck
|
|
1040
|
+
? h('span', {
|
|
1041
|
+
className: 'muted',
|
|
1042
|
+
title: 'GitHub 直连不可用,已通过镜像检查',
|
|
1043
|
+
}, '镜像')
|
|
1044
|
+
: null,
|
|
1045
|
+
releaseUrl
|
|
1046
|
+
? h('a', {
|
|
1047
|
+
href: releaseUrl,
|
|
1048
|
+
target: '_blank',
|
|
1049
|
+
rel: 'noopener noreferrer',
|
|
1050
|
+
title: '查看发布说明',
|
|
1051
|
+
}, '发布页')
|
|
1052
|
+
: null,
|
|
1053
|
+
isDevDeployment && devUpdateCommand
|
|
1054
|
+
? h('button', {
|
|
1055
|
+
type: 'button',
|
|
1056
|
+
className: 'versionLink',
|
|
1057
|
+
title: '查看源码部署的更新命令',
|
|
1058
|
+
onClick: () => setVersionCommandOpen(!versionCommandOpen),
|
|
1059
|
+
}, versionCommandOpen ? '收起命令' : '手动更新')
|
|
1060
|
+
: null,
|
|
1061
|
+
h('button', {
|
|
1062
|
+
type: 'button',
|
|
1063
|
+
className: 'versionLink',
|
|
1064
|
+
title: '重新读取 Miloco 版本与更新状态',
|
|
1065
|
+
disabled: versionBusy,
|
|
1066
|
+
onClick: () => loadVersion(true),
|
|
1067
|
+
}, versionBusy ? '检查中…' : '检查更新'),
|
|
1068
|
+
upgradeUnavailable
|
|
1069
|
+
? h('span', {
|
|
1070
|
+
className: 'versionDirty',
|
|
1071
|
+
title: upgradeInfo?.error || 'GitHub 与镜像均不可达',
|
|
1072
|
+
}, '更新检查不可用')
|
|
1073
|
+
: null,
|
|
1074
|
+
isDevDeployment && gitDirty
|
|
1075
|
+
? h('span', {
|
|
1076
|
+
className: 'versionDirty',
|
|
1077
|
+
title: '源码工作区有本地改动,更新前请先处理或提交',
|
|
1078
|
+
}, '工作区有改动')
|
|
1079
|
+
: null,
|
|
1080
|
+
versionError
|
|
1081
|
+
? h('span', { className: 'versionDirty', title: versionError }, '版本读取失败')
|
|
1082
|
+
: null,
|
|
1083
|
+
upgradePhase && !['idle', 'done', 'failed'].includes(upgradePhase)
|
|
1084
|
+
? h('span', null, `升级中:${upgradePhaseLabels[upgradePhase] || upgradePhase}`)
|
|
1085
|
+
: null)),
|
|
1086
|
+
hasUpdate && !isDevDeployment
|
|
1087
|
+
? h('div', { className: 'statusEnd' },
|
|
1088
|
+
h('button', {
|
|
1089
|
+
type: 'button',
|
|
1090
|
+
className: 'primary',
|
|
1091
|
+
disabled: upgradeBusy,
|
|
1092
|
+
onClick: () => setUpgradeConfirmOpen(!upgradeConfirmOpen),
|
|
1093
|
+
}, upgradeConfirmOpen ? '收起' : '更新'))
|
|
1094
|
+
: null),
|
|
1095
|
+
isDevDeployment && devUpdateCommand && versionCommandOpen
|
|
1096
|
+
? h('div', { className: 'versionManualPanel' },
|
|
1097
|
+
h('code', { className: 'versionCommand' }, devUpdateCommand),
|
|
1098
|
+
h('div', { className: 'row' },
|
|
1099
|
+
h('button', {
|
|
1100
|
+
type: 'button',
|
|
1101
|
+
onClick: () => {
|
|
1102
|
+
try { void navigator.clipboard?.writeText(devUpdateCommand) } catch { /* ignore */ }
|
|
1103
|
+
},
|
|
1104
|
+
}, '复制命令')))
|
|
1105
|
+
: null,
|
|
1106
|
+
upgradePhase === 'failed'
|
|
1107
|
+
? h('div', { className: 'muted versionNote' }, `上次升级失败${upgradeLogPath ? `,可查看 ${upgradeLogPath}` : ',可查看 Miloco 的 upgrade.log'} 后重试。`)
|
|
1108
|
+
: null,
|
|
1109
|
+
upgradeConfirmOpen
|
|
1110
|
+
? h('div', { className: 'danger' },
|
|
1111
|
+
h('h4', null, '更新 Miloco'),
|
|
1112
|
+
h('div', { className: 'muted' },
|
|
1113
|
+
'升级会短暂中断感知与自动化,完成后 Miloco 会自动重启。'),
|
|
1114
|
+
h('div', { className: 'row' },
|
|
1115
|
+
h('button', { type: 'button', onClick: () => setUpgradeConfirmOpen(false) }, '取消'),
|
|
1116
|
+
h('button', {
|
|
1117
|
+
type: 'button',
|
|
1118
|
+
className: 'primary',
|
|
1119
|
+
disabled: upgradeBusy,
|
|
1120
|
+
onClick: runUpgrade,
|
|
1121
|
+
}, upgradeBusy ? '启动中…' : '确认更新')))
|
|
1122
|
+
: null,
|
|
1123
|
+
upgradePolling
|
|
1124
|
+
? h('div', { className: 'muted versionNote' }, '正在等待升级完成…')
|
|
1125
|
+
: null,
|
|
1126
|
+
h('div', { className: 'statusRow' },
|
|
1127
|
+
h('span', { className: 'statusLabel' }, '配置位置'),
|
|
1128
|
+
h('div', { className: 'statusValue' },
|
|
1129
|
+
h('span', { className: 'muted' },
|
|
1130
|
+
`插件默认:${status.configLocation?.bundle ?? ''};`
|
|
1131
|
+
+ `profile 覆盖:${status.configLocation?.profile ?? ''}`))),
|
|
574
1132
|
h('div', { className: 'row' },
|
|
575
|
-
h('button', { type: 'button', disabled: busy, onClick: reload }, '刷新'),
|
|
576
|
-
h('button', { type: 'button', disabled: busy, onClick: () => setConnectOpen(!connectOpen) },
|
|
577
|
-
connectOpen ? '收起连接设置' : '修改连接'),
|
|
578
1133
|
h('button', {
|
|
579
1134
|
type: 'button',
|
|
1135
|
+
disabled: busy,
|
|
1136
|
+
onClick: () => {
|
|
1137
|
+
const opening = !connectOpen
|
|
1138
|
+
setConnectOpen(opening)
|
|
1139
|
+
if (opening) setUninstallOpen(false)
|
|
1140
|
+
},
|
|
1141
|
+
}, '修改连接'),
|
|
1142
|
+
h('button', {
|
|
1143
|
+
type: 'button',
|
|
1144
|
+
className: 'dangerBtn',
|
|
580
1145
|
disabled: uninstallBusy,
|
|
581
|
-
onClick: () =>
|
|
582
|
-
|
|
1146
|
+
onClick: () => {
|
|
1147
|
+
const opening = !uninstallOpen
|
|
1148
|
+
setUninstallOpen(opening)
|
|
1149
|
+
if (opening) setConnectOpen(false)
|
|
1150
|
+
},
|
|
1151
|
+
}, '卸载 Miloco')),
|
|
583
1152
|
reachable ? connectForm : null,
|
|
584
1153
|
uninstallResult ? h('div', { className: 'muted' }, uninstallResult) : null,
|
|
585
1154
|
uninstallOpen
|
|
@@ -608,7 +1177,11 @@ window.__ModuleLoader__.load({
|
|
|
608
1177
|
}),
|
|
609
1178
|
h('button', {
|
|
610
1179
|
type: 'button',
|
|
611
|
-
|
|
1180
|
+
onClick: () => { setUninstallOpen(false); setConfirmText('') },
|
|
1181
|
+
}, '取消'),
|
|
1182
|
+
h('button', {
|
|
1183
|
+
type: 'button',
|
|
1184
|
+
className: 'dangerBtn',
|
|
612
1185
|
disabled: uninstallBusy || confirmText !== 'UNINSTALL',
|
|
613
1186
|
onClick: uninstall,
|
|
614
1187
|
}, uninstallBusy ? '卸载中…' : '确认卸载')))
|
|
@@ -623,6 +1196,7 @@ window.__ModuleLoader__.load({
|
|
|
623
1196
|
|
|
624
1197
|
function apply(ctx) {
|
|
625
1198
|
ensureStyles()
|
|
1199
|
+
installPresetGuide(ctx)
|
|
626
1200
|
ctx.slots.inject('settings.plugins.tab', () => ctx.slots.register({
|
|
627
1201
|
name: 'settings.plugins.tab',
|
|
628
1202
|
id: TAB_ID,
|