agent-loft 0.2.33 → 0.2.35
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 +11 -2
- package/package.json +7 -1
- package/src/channels/web/api.ts +43 -1
- package/src/channels/web/desktop.ts +183 -0
- package/src/channels/web/server.ts +64 -2
- package/src/channels/web/terminal.ts +154 -0
- package/src/core/config.ts +27 -0
- package/src/core/desktop.ts +281 -0
- package/src/core/hub.ts +7 -1
- package/src/core/model-prices.json +103 -0
- package/src/core/pricing.ts +93 -0
- package/src/core/terminals.ts +308 -0
- package/src/core/usage.ts +78 -4
- package/src/core/users.ts +16 -1
- package/web/css/remote.css +350 -0
- package/web/css/settings.css +194 -0
- package/web/css/terminal.css +243 -0
- package/web/index.html +2 -0
- package/web/lib/dom.js +13 -0
- package/web/lib/ws.js +6 -1
- package/web/settings/general.js +96 -0
- package/web/settings/index.js +4 -1
- package/web/settings/usage.js +272 -0
- package/web/settings/users.js +23 -1
- package/web/state.js +12 -1
- package/web/views/desktop-touch.js +344 -0
- package/web/views/desktop.js +507 -0
- package/web/views/layout.js +57 -1
- package/web/views/messages.js +3 -1
- package/web/views/remote.js +136 -0
- package/web/views/sidebar.js +6 -1
- package/web/views/tasks.js +2 -0
- package/web/views/terminal-touch.js +207 -0
- package/web/views/terminal.js +540 -0
- package/web/views/topbar.js +5 -4
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/* ---------- 终端(views/terminal.js) ---------- */
|
|
2
|
+
|
|
3
|
+
/* 底部面板:在输入框下面,高度可拖动 */
|
|
4
|
+
.term-dock {
|
|
5
|
+
--term-h: 38vh;
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
height: var(--term-h);
|
|
9
|
+
min-height: 120px;
|
|
10
|
+
flex-shrink: 0;
|
|
11
|
+
border-top: 1px solid var(--border);
|
|
12
|
+
background: var(--bg);
|
|
13
|
+
position: relative;
|
|
14
|
+
z-index: 6;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.term-dock[hidden] {
|
|
18
|
+
display: none;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.term-resizer {
|
|
22
|
+
position: absolute;
|
|
23
|
+
top: -3px;
|
|
24
|
+
left: 0;
|
|
25
|
+
right: 0;
|
|
26
|
+
height: 6px;
|
|
27
|
+
cursor: row-resize;
|
|
28
|
+
z-index: 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
body.resizing-v {
|
|
32
|
+
cursor: row-resize;
|
|
33
|
+
user-select: none;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.term-head {
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
gap: 2px;
|
|
40
|
+
padding: 4px 8px 0;
|
|
41
|
+
min-height: 36px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.term-head .grow {
|
|
45
|
+
flex: 1;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.term-act {
|
|
49
|
+
width: 30px;
|
|
50
|
+
height: 30px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.term-act[hidden] {
|
|
54
|
+
display: none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.term-act svg {
|
|
58
|
+
width: 16px;
|
|
59
|
+
height: 16px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.term-tabs {
|
|
63
|
+
display: flex;
|
|
64
|
+
gap: 2px;
|
|
65
|
+
min-width: 0;
|
|
66
|
+
overflow-x: auto;
|
|
67
|
+
scrollbar-width: none;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.term-tab {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: 6px;
|
|
74
|
+
max-width: 200px;
|
|
75
|
+
padding: 5px 6px 5px 10px;
|
|
76
|
+
border-radius: 7px;
|
|
77
|
+
font-size: 13px;
|
|
78
|
+
color: var(--fg-soft);
|
|
79
|
+
cursor: pointer;
|
|
80
|
+
white-space: nowrap;
|
|
81
|
+
flex-shrink: 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.term-tab:hover {
|
|
85
|
+
background: var(--bg-hover);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.term-tab.on {
|
|
89
|
+
background: var(--bg-soft);
|
|
90
|
+
color: var(--fg);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.term-tab.ended .term-tab-name {
|
|
94
|
+
text-decoration: line-through;
|
|
95
|
+
color: var(--fg-faint);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.term-tab-name {
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
text-overflow: ellipsis;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.term-tab-x {
|
|
104
|
+
display: grid;
|
|
105
|
+
place-items: center;
|
|
106
|
+
width: 18px;
|
|
107
|
+
height: 18px;
|
|
108
|
+
padding: 0;
|
|
109
|
+
border: none;
|
|
110
|
+
border-radius: 4px;
|
|
111
|
+
background: transparent;
|
|
112
|
+
color: inherit;
|
|
113
|
+
opacity: 0;
|
|
114
|
+
cursor: pointer;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.term-tab-x svg {
|
|
118
|
+
width: 12px;
|
|
119
|
+
height: 12px;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.term-tab:hover .term-tab-x,
|
|
123
|
+
.term-tab.on .term-tab-x {
|
|
124
|
+
opacity: 0.7;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* 触屏上没有悬停:× 一直显示 */
|
|
128
|
+
@media (hover: none) {
|
|
129
|
+
.term-tab .term-tab-x {
|
|
130
|
+
opacity: 0.7;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.term-tab-x:hover {
|
|
135
|
+
opacity: 1;
|
|
136
|
+
background: var(--bg-hover);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.term-panes {
|
|
140
|
+
flex: 1;
|
|
141
|
+
min-height: 0;
|
|
142
|
+
position: relative;
|
|
143
|
+
padding: 4px 4px 4px 10px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.term-pane {
|
|
147
|
+
position: absolute;
|
|
148
|
+
inset: 4px 4px 4px 10px;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.term-pane[hidden] {
|
|
152
|
+
display: none;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.term-pane .xterm {
|
|
156
|
+
height: 100%;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.term-pane .xterm-viewport {
|
|
160
|
+
background: transparent !important;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.term-empty {
|
|
164
|
+
display: flex;
|
|
165
|
+
flex-direction: column;
|
|
166
|
+
align-items: center;
|
|
167
|
+
justify-content: center;
|
|
168
|
+
gap: 12px;
|
|
169
|
+
height: 100%;
|
|
170
|
+
padding: 16px;
|
|
171
|
+
text-align: center;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* 手机上的快捷键行 */
|
|
175
|
+
.term-keys {
|
|
176
|
+
display: flex;
|
|
177
|
+
gap: 6px;
|
|
178
|
+
padding: 6px 8px calc(6px + env(safe-area-inset-bottom));
|
|
179
|
+
overflow-x: auto;
|
|
180
|
+
scrollbar-width: none;
|
|
181
|
+
border-top: 1px solid var(--border);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.term-key {
|
|
185
|
+
flex-shrink: 0;
|
|
186
|
+
min-width: 40px;
|
|
187
|
+
height: 34px;
|
|
188
|
+
padding: 0 10px;
|
|
189
|
+
border: 1px solid var(--border);
|
|
190
|
+
border-radius: 8px;
|
|
191
|
+
background: var(--bg-soft);
|
|
192
|
+
color: var(--fg);
|
|
193
|
+
font: 14px var(--mono);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.term-key.on {
|
|
197
|
+
background: var(--accent);
|
|
198
|
+
border-color: var(--accent);
|
|
199
|
+
color: var(--accent-fg);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* 放在远程控制页里:占满那一块,不能拖高度 */
|
|
203
|
+
.term-dock.embedded {
|
|
204
|
+
flex: 1;
|
|
205
|
+
height: auto;
|
|
206
|
+
min-height: 0;
|
|
207
|
+
border-top: none;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.term-dock.embedded .term-resizer {
|
|
211
|
+
display: none;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* 手机上长按选了字:上面一条「复制 / 全选 / 取消」(views/terminal-touch.js) */
|
|
215
|
+
.term-selbar {
|
|
216
|
+
position: absolute;
|
|
217
|
+
top: 6px;
|
|
218
|
+
right: 6px;
|
|
219
|
+
z-index: 5;
|
|
220
|
+
display: flex;
|
|
221
|
+
gap: 1px;
|
|
222
|
+
border-radius: 10px;
|
|
223
|
+
overflow: hidden;
|
|
224
|
+
box-shadow: var(--shadow);
|
|
225
|
+
background: var(--border);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.term-selbar[hidden] {
|
|
229
|
+
display: none;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.term-selbar button {
|
|
233
|
+
padding: 8px 14px;
|
|
234
|
+
border: none;
|
|
235
|
+
background: var(--bg-input);
|
|
236
|
+
color: var(--fg);
|
|
237
|
+
font: 14px var(--font);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* 手指翻历史时别让浏览器去滚页面 */
|
|
241
|
+
.term-pane {
|
|
242
|
+
touch-action: none;
|
|
243
|
+
}
|
package/web/index.html
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
<link rel="stylesheet" href="/css/schedule.css" />
|
|
27
27
|
<link rel="stylesheet" href="/css/tasks.css" />
|
|
28
28
|
<link rel="stylesheet" href="/css/skills.css" />
|
|
29
|
+
<link rel="stylesheet" href="/css/terminal.css" />
|
|
30
|
+
<link rel="stylesheet" href="/css/remote.css" />
|
|
29
31
|
<link rel="stylesheet" href="/css/mobile.css" />
|
|
30
32
|
</head>
|
|
31
33
|
<body>
|
package/web/lib/dom.js
CHANGED
|
@@ -46,6 +46,19 @@ export const ICON = {
|
|
|
46
46
|
trash: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6M14 11v6"/></svg>',
|
|
47
47
|
folder: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7.5A2.5 2.5 0 0 1 5.5 5h3.6l2 2.2h7.4A2.5 2.5 0 0 1 21 9.7v7.8a2.5 2.5 0 0 1-2.5 2.5h-13A2.5 2.5 0 0 1 3 17.5Z"/><path d="M3 10h18"/></svg>',
|
|
48
48
|
folderOpen: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 17.5V7.5A2.5 2.5 0 0 1 5.5 5h3.6l2 2.2h6.4A2.5 2.5 0 0 1 20 9.7V11"/><path d="M3 17.5 5.6 12a2 2 0 0 1 1.8-1.1h13a1 1 0 0 1 .9 1.4l-2.4 6.2a2.2 2.2 0 0 1-2 1.5H5.5A2.5 2.5 0 0 1 3 17.5Z"/></svg>',
|
|
49
|
+
terminal: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="m7 9 3 3-3 3M13 15h4"/></svg>',
|
|
50
|
+
monitor: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>',
|
|
51
|
+
keyboard: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M6 9h.01M10 9h.01M14 9h.01M18 9h.01M6 13h.01M18 13h.01M10 13h4M7 16h10"/></svg>',
|
|
52
|
+
clipboard: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="8" y="2" width="8" height="4" rx="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/></svg>',
|
|
53
|
+
command: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3"/></svg>',
|
|
54
|
+
eye: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7S2 12 2 12Z"/><circle cx="12" cy="12" r="3"/></svg>',
|
|
55
|
+
expand: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M16 21h3a2 2 0 0 0 2-2v-3"/></svg>',
|
|
56
|
+
power: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v10"/><path d="M18.4 6.6a9 9 0 1 1-12.8 0"/></svg>',
|
|
57
|
+
pointer: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 3l14 7-6 2-2 6Z"/></svg>',
|
|
58
|
+
hand: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11V5a1.5 1.5 0 0 1 3 0v5"/><path d="M12 10V4a1.5 1.5 0 0 1 3 0v6"/><path d="M15 10V6a1.5 1.5 0 0 1 3 0v8a7 7 0 0 1-7 7h-.5a6 6 0 0 1-4.6-2.2L3.4 15a1.6 1.6 0 0 1 2.4-2.1L9 15.5V11"/></svg>',
|
|
59
|
+
help: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M9.5 9.2a2.6 2.6 0 0 1 5 .9c0 1.7-2.5 2.3-2.5 3.9M12 17h.01"/></svg>',
|
|
60
|
+
maximize: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>',
|
|
61
|
+
restore: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 14h6v6M20 10h-6V4M14 10l7-7M3 21l7-7"/></svg>',
|
|
49
62
|
laptop: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="5" width="16" height="11" rx="1.5"/><path d="M2 19h20"/></svg>',
|
|
50
63
|
logo: '<svg viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 8.5a8 8 0 0 1 13.2 0 8 8 0 0 1 9 9.4 8 8 0 0 1 0 12.2 8 8 0 0 1-9 9.4 8 8 0 0 1-13.2 0 8 8 0 0 1-9-9.4 8 8 0 0 1 0-12.2 8 8 0 0 1 9-9.4Z"/><path d="m17 18 5 5-5 5M25 30h7"/></svg>',
|
|
51
64
|
feishu:
|
package/web/lib/ws.js
CHANGED
|
@@ -98,7 +98,8 @@ export const WS = {
|
|
|
98
98
|
*/
|
|
99
99
|
presence() {
|
|
100
100
|
const visible = document.visibilityState === "visible" && document.hasFocus() && Date.now() - lastInput < IDLE_MS;
|
|
101
|
-
|
|
101
|
+
// 在定时任务、远程控制页上:没在看哪条对话(远程控制页不清 S.current,回来时对话还在)
|
|
102
|
+
this.raw({ t: "presence", visible, key: S.view ? "" : (S.current ?? "") });
|
|
102
103
|
},
|
|
103
104
|
};
|
|
104
105
|
|
|
@@ -241,6 +242,10 @@ function onWsMessage(msg) {
|
|
|
241
242
|
// 自己的定时任务变了:定时任务页开着的话会去重新拉(views/tasks.js)
|
|
242
243
|
document.dispatchEvent(new Event("aim:tasks"));
|
|
243
244
|
return;
|
|
245
|
+
case "remote":
|
|
246
|
+
// 自己的终端开了 / 关了,或者远程控制的权限、设置变了:重新问一下(views/layout.js)
|
|
247
|
+
document.dispatchEvent(new Event("aim:remote"));
|
|
248
|
+
return;
|
|
244
249
|
case "skills":
|
|
245
250
|
// 技能变了:设置里的技能页开着就重新拉,@ 补全的缓存作废
|
|
246
251
|
document.dispatchEvent(new Event("aim:skills"));
|
package/web/settings/general.js
CHANGED
|
@@ -98,6 +98,102 @@ export function generalTab(cfg, panel) {
|
|
|
98
98
|
),
|
|
99
99
|
),
|
|
100
100
|
),
|
|
101
|
+
remoteSection(cfg),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 远程控制:终端、桌面各自的开关,没人连着多久结束,桌面从哪来。成员能不能用在「用户」里一个个开 */
|
|
106
|
+
function remoteSection(cfg) {
|
|
107
|
+
const r = cfg.remote ?? {};
|
|
108
|
+
const termOn = h("input", { type: "checkbox", checked: r.terminal !== false });
|
|
109
|
+
const deskOn = h("input", { type: "checkbox", checked: r.desktop !== false });
|
|
110
|
+
const keep = h("input", { type: "number", min: 1, max: 1440, value: r.keepAliveMin ?? 30 });
|
|
111
|
+
const source = h(
|
|
112
|
+
"select",
|
|
113
|
+
{},
|
|
114
|
+
h("option", { value: "auto" }, "共享这台机器当前的桌面(Linux,用 x11vnc)"),
|
|
115
|
+
h("option", { value: "vnc" }, "连已有的 VNC 服务(macOS 屏幕共享等)"),
|
|
116
|
+
);
|
|
117
|
+
source.value = r.desktopSource ?? "auto";
|
|
118
|
+
const display = h("input", { value: r.display ?? "", placeholder: "留空自动找,如 :0", autocapitalize: "off", spellcheck: "false" });
|
|
119
|
+
const host = h("input", { value: r.vncHost ?? "127.0.0.1", autocapitalize: "off", spellcheck: "false" });
|
|
120
|
+
const port = h("input", { type: "number", min: 1, max: 65535, value: r.vncPort ?? 5900 });
|
|
121
|
+
const password = h("input", { type: "password", value: r.vncPassword ?? "", autocomplete: "new-password", placeholder: "VNC 密码" });
|
|
122
|
+
const autoBox = h("div", { class: "field-group" }, field("X 显示", display, "一台机器上有好几个桌面(比如登录界面 :0、登录后的桌面 :1)时可以指定"));
|
|
123
|
+
const vncBox = h(
|
|
124
|
+
"div",
|
|
125
|
+
{ class: "field-group" },
|
|
126
|
+
field("地址", host),
|
|
127
|
+
field("端口", port),
|
|
128
|
+
field(
|
|
129
|
+
"密码",
|
|
130
|
+
password,
|
|
131
|
+
"macOS:屏幕共享 →「电脑设置…」里勾选「VNC 显示程序可以使用密码控制屏幕」并设这个密码。开通了远程控制的成员连桌面时,页面会拿到这个密码",
|
|
132
|
+
),
|
|
133
|
+
);
|
|
134
|
+
const sync = () => {
|
|
135
|
+
autoBox.hidden = source.value !== "auto";
|
|
136
|
+
vncBox.hidden = source.value !== "vnc";
|
|
137
|
+
};
|
|
138
|
+
source.addEventListener("change", sync);
|
|
139
|
+
sync();
|
|
140
|
+
// 终端、桌面用不了时的原因(没装 node-pty、没有图形桌面、没装 x11vnc…)
|
|
141
|
+
const status = h("div", { class: "error-text", hidden: true });
|
|
142
|
+
const loadStatus = () =>
|
|
143
|
+
api("/api/remote").then(
|
|
144
|
+
(st) => {
|
|
145
|
+
const lines = [
|
|
146
|
+
st.terminal.available ? null : `终端:${st.terminal.reason ?? "用不了"}`,
|
|
147
|
+
st.desktop.available ? null : `桌面:${st.desktop.reason ?? "这台机器没有桌面"}(远程控制里只有终端)`,
|
|
148
|
+
].filter(Boolean);
|
|
149
|
+
fill(status, lines.map((l, i) => [i ? h("br") : null, l]));
|
|
150
|
+
status.hidden = !lines.length;
|
|
151
|
+
},
|
|
152
|
+
() => {},
|
|
153
|
+
);
|
|
154
|
+
void loadStatus();
|
|
155
|
+
return h(
|
|
156
|
+
"div",
|
|
157
|
+
{ class: "section" },
|
|
158
|
+
h("h4", {}, "远程控制"),
|
|
159
|
+
h(
|
|
160
|
+
"div",
|
|
161
|
+
{ class: "muted" },
|
|
162
|
+
"侧栏「远程控制」里直接操作这台机器的桌面和终端,切回对话时连接留在后台。以运行 agent-loft 的系统用户执行,没有授权卡。管理员能用;成员要在「用户」里单独开通。",
|
|
163
|
+
),
|
|
164
|
+
status,
|
|
165
|
+
h("label", { class: "check" }, termOn, "网页终端"),
|
|
166
|
+
h("label", { class: "check" }, deskOn, "远程桌面"),
|
|
167
|
+
field("桌面来源", source),
|
|
168
|
+
autoBox,
|
|
169
|
+
vncBox,
|
|
170
|
+
field("没有页面连着多久后结束(分钟)", keep, "关掉页面、断线后 shell 继续运行,重新打开能接着用;共享桌面用的 x11vnc 也是这么久没人连就停掉"),
|
|
171
|
+
h(
|
|
172
|
+
"div",
|
|
173
|
+
{ class: "row" },
|
|
174
|
+
h(
|
|
175
|
+
"button",
|
|
176
|
+
{
|
|
177
|
+
class: "btn primary",
|
|
178
|
+
onclick: async () => {
|
|
179
|
+
const ok = await saveConfig(cfg, {
|
|
180
|
+
remote: {
|
|
181
|
+
terminal: termOn.checked,
|
|
182
|
+
desktop: deskOn.checked,
|
|
183
|
+
keepAliveMin: Number(keep.value),
|
|
184
|
+
desktopSource: source.value,
|
|
185
|
+
display: display.value.trim(),
|
|
186
|
+
vncHost: host.value.trim(),
|
|
187
|
+
vncPort: Number(port.value),
|
|
188
|
+
vncPassword: password.value,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
if (ok) void loadStatus();
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
"保存",
|
|
195
|
+
),
|
|
196
|
+
),
|
|
101
197
|
);
|
|
102
198
|
}
|
|
103
199
|
|
package/web/settings/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { feishuTab } from "./feishu.js";
|
|
|
10
10
|
import { authTab, securityTab } from "./security.js";
|
|
11
11
|
import { memoryTab } from "./memory.js";
|
|
12
12
|
import { skillsTab } from "./skills.js";
|
|
13
|
+
import { usageTab } from "./usage.js";
|
|
13
14
|
import { systemTab, usersTab } from "./users.js";
|
|
14
15
|
|
|
15
16
|
// ---------- 设置 ----------
|
|
@@ -20,11 +21,12 @@ export async function openSettings(tab) {
|
|
|
20
21
|
const panel = h("div", { class: "settings-body" });
|
|
21
22
|
// 设置弹窗高度固定,切标签页时不跟着内容一伸一缩
|
|
22
23
|
modal({ title: "设置", wide: true, tall: true, body: h("div", { class: "settings" }, nav, panel) });
|
|
23
|
-
//
|
|
24
|
+
// 普通成员只能管自己的记忆、技能、用量、通知、密码和登录设备;其余设置都是管理员的
|
|
24
25
|
if (!isAdmin()) {
|
|
25
26
|
const tabs = [
|
|
26
27
|
["记忆", () => memoryTab(panel)],
|
|
27
28
|
["技能", () => skillsTab(panel)],
|
|
29
|
+
["用量", () => usageTab(panel)],
|
|
28
30
|
["通知", () => notifyTab(panel)],
|
|
29
31
|
["账号", () => securityTab(panel)],
|
|
30
32
|
];
|
|
@@ -52,6 +54,7 @@ export async function openSettings(tab) {
|
|
|
52
54
|
["飞书机器人", () => feishuTab(cfg, panel)],
|
|
53
55
|
["授权", () => authTab(panel)],
|
|
54
56
|
["记忆", () => memoryTab(panel)],
|
|
57
|
+
["用量", () => usageTab(panel)],
|
|
55
58
|
["通知", () => notifyTab(panel)],
|
|
56
59
|
["账号", () => securityTab(panel)],
|
|
57
60
|
["用户", () => usersTab(panel)],
|