agent-loft 0.2.30 → 0.2.32
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 -1
- package/package.json +1 -1
- package/src/agents/claude/driver.ts +13 -8
- package/src/agents/claude/session.ts +38 -6
- package/src/agents/codex/session.ts +32 -5
- package/src/channels/feishu/bot.ts +8 -4
- package/src/channels/feishu/turn-stream.ts +3 -2
- package/src/channels/web/api.ts +8 -0
- package/src/channels/web/http.ts +2 -2
- package/src/channels/web/server.ts +21 -1
- package/src/channels/web/skills-api.ts +225 -0
- package/src/channels/web/tasks-api.ts +94 -0
- package/src/core/chat-mode.ts +16 -16
- package/src/core/command-catalog.ts +8 -1
- package/src/core/commands.ts +3 -0
- package/src/core/hub.ts +147 -19
- package/src/core/memory-mcp.ts +1 -1
- package/src/core/personal.ts +5 -1
- package/src/core/skill-runtime.ts +359 -0
- package/src/core/skills-bridge.ts +288 -0
- package/src/core/skills.ts +675 -0
- package/src/core/store.ts +2 -0
- package/src/core/task-prompt.ts +38 -0
- package/src/core/task-runner.ts +232 -0
- package/src/core/task-schedule.ts +250 -0
- package/src/core/tasks-bridge.ts +256 -0
- package/src/core/tasks-mcp.ts +66 -0
- package/src/core/tasks.ts +281 -0
- package/src/core/types.ts +21 -0
- package/src/core/zip.ts +98 -0
- package/src/main.ts +12 -0
- package/src/mcp/tasks-server.ts +327 -0
- package/web/css/skills.css +368 -0
- package/web/css/tasks.css +455 -0
- package/web/index.html +2 -0
- package/web/lib/dom.js +1 -0
- package/web/lib/ws.js +8 -0
- package/web/state.js +2 -0
- package/web/views/composer.js +15 -3
- package/web/views/conv-actions.js +15 -7
- package/web/views/layout.js +22 -1
- package/web/views/mention.js +193 -0
- package/web/views/messages.js +23 -2
- package/web/views/sidebar.js +4 -0
- package/web/views/skills.js +703 -0
- package/web/views/tasks.js +469 -0
- package/web/views/topbar.js +9 -3
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/* ---------- 定时任务 ---------- */
|
|
2
|
+
|
|
3
|
+
/* 侧栏「最近」上面的入口:和「新聊天」同一个样子 */
|
|
4
|
+
.side-nav {
|
|
5
|
+
display: flex;
|
|
6
|
+
justify-content: flex-start;
|
|
7
|
+
width: calc(100% - 20px);
|
|
8
|
+
margin: 2px 10px 6px;
|
|
9
|
+
padding: 7px 10px;
|
|
10
|
+
gap: 8px;
|
|
11
|
+
background: transparent;
|
|
12
|
+
border: none;
|
|
13
|
+
font-size: 14px;
|
|
14
|
+
font-weight: 400;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.side-nav svg {
|
|
18
|
+
width: 16px;
|
|
19
|
+
height: 16px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.side-nav:hover,
|
|
23
|
+
.side-nav.active {
|
|
24
|
+
background: var(--bg-hover);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.conv-item .task-mark {
|
|
28
|
+
color: var(--fg-soft);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* 任务页不要输入框,也不要「回到底部」 */
|
|
32
|
+
.main.tasks-view .composer-wrap,
|
|
33
|
+
.main.tasks-view .to-bottom {
|
|
34
|
+
display: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.tasks-page {
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
gap: 16px;
|
|
41
|
+
padding-top: 8px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.tasks-head {
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: flex-start;
|
|
47
|
+
gap: 16px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.tasks-head .grow {
|
|
51
|
+
flex: 1;
|
|
52
|
+
min-width: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.tasks-head h2 {
|
|
56
|
+
margin: 0 0 6px;
|
|
57
|
+
font-size: 24px;
|
|
58
|
+
font-weight: 600;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.tasks-head p {
|
|
62
|
+
margin: 0;
|
|
63
|
+
font-size: 13px;
|
|
64
|
+
line-height: 1.6;
|
|
65
|
+
color: var(--fg-soft);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.tasks-head .btn {
|
|
69
|
+
flex-shrink: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.task-list {
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
gap: 12px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.task-card {
|
|
79
|
+
border: 1px solid var(--border);
|
|
80
|
+
border-radius: var(--radius);
|
|
81
|
+
padding: 12px 14px 12px 16px;
|
|
82
|
+
background: var(--bg);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.task-card.off .task-title,
|
|
86
|
+
.task-card.off .task-meta,
|
|
87
|
+
.task-card.off .task-prompt {
|
|
88
|
+
opacity: 0.6;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.task-top {
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: 8px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.task-title {
|
|
98
|
+
flex: 1;
|
|
99
|
+
min-width: 0;
|
|
100
|
+
padding: 0;
|
|
101
|
+
text-align: left;
|
|
102
|
+
font-size: 16px;
|
|
103
|
+
font-weight: 600;
|
|
104
|
+
overflow: hidden;
|
|
105
|
+
text-overflow: ellipsis;
|
|
106
|
+
white-space: nowrap;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.task-meta {
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-wrap: wrap;
|
|
112
|
+
gap: 2px 0;
|
|
113
|
+
margin-top: 4px;
|
|
114
|
+
font-size: 13px;
|
|
115
|
+
color: var(--fg-soft);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* 分隔点挂在前一项的末尾:换行时点留在行尾,不会出现在下一行开头 */
|
|
119
|
+
.task-meta span:not(:last-child)::after {
|
|
120
|
+
content: "·";
|
|
121
|
+
margin: 0 8px;
|
|
122
|
+
color: var(--fg-faint);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.task-prompt {
|
|
126
|
+
margin-top: 8px;
|
|
127
|
+
font-size: 13px;
|
|
128
|
+
line-height: 1.6;
|
|
129
|
+
color: var(--fg-soft);
|
|
130
|
+
white-space: pre-wrap;
|
|
131
|
+
display: -webkit-box;
|
|
132
|
+
-webkit-line-clamp: 2;
|
|
133
|
+
-webkit-box-orient: vertical;
|
|
134
|
+
overflow: hidden;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.task-run {
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
gap: 8px;
|
|
141
|
+
width: 100%;
|
|
142
|
+
min-width: 0;
|
|
143
|
+
margin-top: 10px;
|
|
144
|
+
padding: 8px 10px;
|
|
145
|
+
border-radius: 10px;
|
|
146
|
+
background: var(--bg-soft);
|
|
147
|
+
font-size: 13px;
|
|
148
|
+
text-align: left;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
button.task-run:hover {
|
|
152
|
+
background: var(--bg-hover);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.task-run.compact {
|
|
156
|
+
margin-top: 2px;
|
|
157
|
+
padding: 6px 10px;
|
|
158
|
+
background: transparent;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.task-run.muted {
|
|
162
|
+
color: var(--fg-faint);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.task-status {
|
|
166
|
+
display: inline-flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
gap: 5px;
|
|
169
|
+
flex-shrink: 0;
|
|
170
|
+
padding: 1px 8px;
|
|
171
|
+
border-radius: 999px;
|
|
172
|
+
font-size: 12px;
|
|
173
|
+
border: 1px solid currentColor;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.task-status .conv-spin {
|
|
177
|
+
width: 10px;
|
|
178
|
+
height: 10px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.task-status.ok {
|
|
182
|
+
color: var(--ok);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.task-status.quiet {
|
|
186
|
+
color: var(--fg-faint);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.task-status.bad {
|
|
190
|
+
color: var(--danger);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.task-status.run {
|
|
194
|
+
color: var(--accent);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.task-run-at {
|
|
198
|
+
flex-shrink: 0;
|
|
199
|
+
color: var(--fg-faint);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.task-run-text {
|
|
203
|
+
flex: 1;
|
|
204
|
+
min-width: 0;
|
|
205
|
+
overflow: hidden;
|
|
206
|
+
text-overflow: ellipsis;
|
|
207
|
+
white-space: nowrap;
|
|
208
|
+
color: var(--fg-soft);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.task-history summary {
|
|
212
|
+
margin-top: 8px;
|
|
213
|
+
font-size: 12px;
|
|
214
|
+
color: var(--fg-faint);
|
|
215
|
+
cursor: pointer;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.task-history summary:hover {
|
|
219
|
+
color: var(--fg-soft);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* 开关 */
|
|
223
|
+
.switch {
|
|
224
|
+
position: relative;
|
|
225
|
+
display: inline-block;
|
|
226
|
+
width: 36px;
|
|
227
|
+
height: 20px;
|
|
228
|
+
flex-shrink: 0;
|
|
229
|
+
cursor: pointer;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.switch input {
|
|
233
|
+
position: absolute;
|
|
234
|
+
opacity: 0;
|
|
235
|
+
width: 0;
|
|
236
|
+
height: 0;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.switch .slider {
|
|
240
|
+
position: absolute;
|
|
241
|
+
inset: 0;
|
|
242
|
+
border-radius: 999px;
|
|
243
|
+
background: var(--border);
|
|
244
|
+
transition: background 0.15s;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.switch .slider::before {
|
|
248
|
+
content: "";
|
|
249
|
+
position: absolute;
|
|
250
|
+
left: 2px;
|
|
251
|
+
top: 2px;
|
|
252
|
+
width: 16px;
|
|
253
|
+
height: 16px;
|
|
254
|
+
border-radius: 50%;
|
|
255
|
+
background: #fff;
|
|
256
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
257
|
+
transition: transform 0.15s;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.switch input:checked + .slider {
|
|
261
|
+
background: var(--accent);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.switch input:checked + .slider::before {
|
|
265
|
+
transform: translateX(16px);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.switch input:focus-visible + .slider {
|
|
269
|
+
outline: 2px solid var(--accent);
|
|
270
|
+
outline-offset: 2px;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* 空状态 + 例子 */
|
|
274
|
+
.tasks-empty {
|
|
275
|
+
display: flex;
|
|
276
|
+
flex-direction: column;
|
|
277
|
+
align-items: center;
|
|
278
|
+
gap: 6px;
|
|
279
|
+
padding: 32px 0 8px;
|
|
280
|
+
text-align: center;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.tasks-empty-icon svg {
|
|
284
|
+
width: 40px;
|
|
285
|
+
height: 40px;
|
|
286
|
+
color: var(--fg-faint);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.tasks-empty .t {
|
|
290
|
+
font-size: 17px;
|
|
291
|
+
font-weight: 600;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.tasks-empty > .s {
|
|
295
|
+
font-size: 13px;
|
|
296
|
+
color: var(--fg-soft);
|
|
297
|
+
margin-bottom: 10px;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.task-examples {
|
|
301
|
+
display: grid;
|
|
302
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
303
|
+
gap: 10px;
|
|
304
|
+
width: 100%;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.task-example {
|
|
308
|
+
text-align: left;
|
|
309
|
+
padding: 12px 14px;
|
|
310
|
+
border: 1px solid var(--border);
|
|
311
|
+
border-radius: 12px;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.task-example:hover {
|
|
315
|
+
background: var(--bg-hover);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.task-example .t {
|
|
319
|
+
font-size: 14px;
|
|
320
|
+
font-weight: 600;
|
|
321
|
+
margin-bottom: 4px;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.task-example .s {
|
|
325
|
+
font-size: 12px;
|
|
326
|
+
line-height: 1.6;
|
|
327
|
+
color: var(--fg-soft);
|
|
328
|
+
display: -webkit-box;
|
|
329
|
+
-webkit-line-clamp: 3;
|
|
330
|
+
-webkit-box-orient: vertical;
|
|
331
|
+
overflow: hidden;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.tasks-tip {
|
|
335
|
+
margin: 4px 0 0;
|
|
336
|
+
font-size: 12px;
|
|
337
|
+
color: var(--fg-faint);
|
|
338
|
+
text-align: center;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/* 表单 */
|
|
342
|
+
.task-form [hidden] {
|
|
343
|
+
display: none !important;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.task-form textarea {
|
|
347
|
+
resize: vertical;
|
|
348
|
+
font: inherit;
|
|
349
|
+
line-height: 1.6;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.task-dedicated {
|
|
353
|
+
display: flex;
|
|
354
|
+
flex-direction: column;
|
|
355
|
+
gap: 12px;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/* 频率:上面选种类,下面填具体时间 */
|
|
359
|
+
.task-freq-row {
|
|
360
|
+
display: flex;
|
|
361
|
+
flex-direction: column;
|
|
362
|
+
gap: 8px;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.task-freq {
|
|
366
|
+
display: flex;
|
|
367
|
+
flex-wrap: wrap;
|
|
368
|
+
align-items: center;
|
|
369
|
+
gap: 8px;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.task-freq:empty {
|
|
373
|
+
display: none;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.field .task-freq input,
|
|
377
|
+
.field .task-freq select {
|
|
378
|
+
width: auto;
|
|
379
|
+
color-scheme: light dark;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.field .task-freq input:not(.narrow):not([type="time"]) {
|
|
383
|
+
flex: 1;
|
|
384
|
+
min-width: 180px;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.field .task-freq .narrow {
|
|
388
|
+
width: 84px;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.freq-word {
|
|
392
|
+
font-size: 14px;
|
|
393
|
+
color: var(--fg-soft);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.day-chips {
|
|
397
|
+
display: flex;
|
|
398
|
+
gap: 4px;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.day-chip {
|
|
402
|
+
width: 34px;
|
|
403
|
+
height: 34px;
|
|
404
|
+
border-radius: 50%;
|
|
405
|
+
border: 1px solid var(--border);
|
|
406
|
+
font-size: 13px;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.day-chip.on {
|
|
410
|
+
background: var(--accent);
|
|
411
|
+
border-color: var(--accent);
|
|
412
|
+
color: var(--accent-fg);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.task-static {
|
|
416
|
+
font-size: 14px;
|
|
417
|
+
color: var(--fg-soft);
|
|
418
|
+
padding: 2px 0;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.task-cwd {
|
|
422
|
+
justify-content: flex-start;
|
|
423
|
+
overflow: hidden;
|
|
424
|
+
text-overflow: ellipsis;
|
|
425
|
+
white-space: nowrap;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/* 对话里:定时任务这次判断不用提醒,折叠成一行 */
|
|
429
|
+
.silent-run {
|
|
430
|
+
font-size: 13px;
|
|
431
|
+
color: var(--fg-faint);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.silent-run summary {
|
|
435
|
+
cursor: pointer;
|
|
436
|
+
list-style: none;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.silent-run summary::-webkit-details-marker {
|
|
440
|
+
display: none;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.silent-run summary:hover {
|
|
444
|
+
color: var(--fg-soft);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.silent-run[open] summary {
|
|
448
|
+
margin-bottom: 8px;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
@media (max-width: 640px) {
|
|
452
|
+
.tasks-head h2 {
|
|
453
|
+
font-size: 21px;
|
|
454
|
+
}
|
|
455
|
+
}
|
package/web/index.html
CHANGED
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
<link rel="stylesheet" href="/css/overlay.css" />
|
|
25
25
|
<link rel="stylesheet" href="/css/settings.css" />
|
|
26
26
|
<link rel="stylesheet" href="/css/schedule.css" />
|
|
27
|
+
<link rel="stylesheet" href="/css/tasks.css" />
|
|
28
|
+
<link rel="stylesheet" href="/css/skills.css" />
|
|
27
29
|
<link rel="stylesheet" href="/css/mobile.css" />
|
|
28
30
|
</head>
|
|
29
31
|
<body>
|
package/web/lib/dom.js
CHANGED
|
@@ -34,6 +34,7 @@ export const ICON = {
|
|
|
34
34
|
clip: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.4 11.6-9.2 9.2a6 6 0 0 1-8.5-8.5l9.2-9.2a4 4 0 0 1 5.7 5.7l-9.2 9.2a2 2 0 0 1-2.8-2.8l8.5-8.5"/></svg>',
|
|
35
35
|
mic: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M5 10a7 7 0 0 0 14 0M12 17v5"/></svg>',
|
|
36
36
|
more: '<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="19" cy="12" r="1.8"/></svg>',
|
|
37
|
+
spark: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9Z"/><path d="M19 15l.8 2.2L22 18l-2.2.8L19 21l-.8-2.2L16 18l2.2-.8Z"/></svg>',
|
|
37
38
|
clock: '<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="M12 7v5l3 2"/></svg>',
|
|
38
39
|
down: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="m6 9 6 6 6-6"/></svg>',
|
|
39
40
|
gear: '<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="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z"/></svg>',
|
package/web/lib/ws.js
CHANGED
|
@@ -197,6 +197,14 @@ function onWsMessage(msg) {
|
|
|
197
197
|
case "ix":
|
|
198
198
|
upsertInteraction(msg.interaction);
|
|
199
199
|
return;
|
|
200
|
+
case "tasks":
|
|
201
|
+
// 自己的定时任务变了:定时任务页开着的话会去重新拉(views/tasks.js)
|
|
202
|
+
document.dispatchEvent(new Event("aim:tasks"));
|
|
203
|
+
return;
|
|
204
|
+
case "skills":
|
|
205
|
+
// 技能变了(自己的、装了的、广场上的):技能页重新拉,@ 补全的缓存作废
|
|
206
|
+
document.dispatchEvent(new Event("aim:skills"));
|
|
207
|
+
return;
|
|
200
208
|
case "notice":
|
|
201
209
|
// 正看着这条会话就不打扰;普通网页靠 Web Push,这里只给桌面端
|
|
202
210
|
if (!desktop) return;
|
package/web/state.js
CHANGED
package/web/views/composer.js
CHANGED
|
@@ -8,6 +8,7 @@ import { addItem, openConversation, openLightbox, renderEmpty, scrollToBottom }
|
|
|
8
8
|
import { renderScheduled } from "./schedule.js";
|
|
9
9
|
import { cancelVoice, SpeechRec, toggleVoice } from "./voice.js";
|
|
10
10
|
import { closeSlashMenu, commandUsage, oneLine, slashKeydown, updateSlashMenu } from "./slash.js";
|
|
11
|
+
import { attachMention } from "./mention.js";
|
|
11
12
|
import { doAction, openModelPicker, openSessions } from "./conv-actions.js";
|
|
12
13
|
|
|
13
14
|
// ---------- 输入框 ----------
|
|
@@ -16,16 +17,21 @@ export function renderComposer() {
|
|
|
16
17
|
cancelVoice();
|
|
17
18
|
ui.input = h("textarea", {
|
|
18
19
|
rows: 1,
|
|
19
|
-
placeholder: "
|
|
20
|
+
placeholder: "随心输入,@ 选技能",
|
|
20
21
|
oninput: () => {
|
|
21
22
|
// 识别过程中用户自己动了输入框,以用户为准,停掉识别
|
|
22
23
|
cancelVoice();
|
|
23
24
|
autosize();
|
|
24
25
|
void updateSlashMenu();
|
|
26
|
+
void ui.mention.update();
|
|
25
27
|
},
|
|
26
|
-
onblur: () =>
|
|
28
|
+
onblur: () =>
|
|
29
|
+
setTimeout(() => {
|
|
30
|
+
closeSlashMenu();
|
|
31
|
+
ui.mention.close();
|
|
32
|
+
}, 150),
|
|
27
33
|
onkeydown: (e) => {
|
|
28
|
-
if (slashKeydown(e)) {
|
|
34
|
+
if (slashKeydown(e) || ui.mention.keydown(e)) {
|
|
29
35
|
e.preventDefault();
|
|
30
36
|
return;
|
|
31
37
|
}
|
|
@@ -62,6 +68,10 @@ export function renderComposer() {
|
|
|
62
68
|
ui.chips = h("div", { class: "chips" });
|
|
63
69
|
ui.schedBar = h("div", { class: "sched-bar", hidden: true });
|
|
64
70
|
ui.slashMenu = h("div", { class: "slash-menu", hidden: true });
|
|
71
|
+
ui.mentionMenu = h("div", { class: "slash-menu", hidden: true });
|
|
72
|
+
ui.mention = attachMention(ui.input, ui.mentionMenu, {
|
|
73
|
+
onEmpty: () => import("./skills.js").then((m) => m.openSkills()),
|
|
74
|
+
});
|
|
65
75
|
ui.composer = h(
|
|
66
76
|
"div",
|
|
67
77
|
{
|
|
@@ -78,6 +88,7 @@ export function renderComposer() {
|
|
|
78
88
|
},
|
|
79
89
|
},
|
|
80
90
|
ui.slashMenu,
|
|
91
|
+
ui.mentionMenu,
|
|
81
92
|
ui.chips,
|
|
82
93
|
ui.input,
|
|
83
94
|
h(
|
|
@@ -253,6 +264,7 @@ export async function sendMessage() {
|
|
|
253
264
|
return;
|
|
254
265
|
}
|
|
255
266
|
closeSlashMenu();
|
|
267
|
+
ui.mention.close();
|
|
256
268
|
let key;
|
|
257
269
|
let res;
|
|
258
270
|
setSending(true);
|
|
@@ -198,12 +198,16 @@ export async function openSessions(all) {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
/** 目录选择器:一层层点进去选,也能手动输入。只能在 workspaceRoot 之内 */
|
|
201
|
-
|
|
201
|
+
/**
|
|
202
|
+
* 选工作目录。pick 给了就是别处借用(定时任务的表单):从 pick.current 开始,选好交给 pick.onPick,
|
|
203
|
+
* 不动当前对话。
|
|
204
|
+
*/
|
|
205
|
+
export async function changeCwd(pick) {
|
|
202
206
|
// 还没发消息的新对话:只记下选择,创建会话时带过去
|
|
203
|
-
const draft = !S.current;
|
|
204
|
-
const conv = draft ? null : S.convs.get(S.current);
|
|
205
|
-
const current = draft ? (S.draftCwd ?? S.workspaceRoot ?? "") : (conv?.cwd ?? "");
|
|
206
|
-
const goLabel = draft ? "使用此目录" : "切换到此目录";
|
|
207
|
+
const draft = !pick && !S.current;
|
|
208
|
+
const conv = draft || pick ? null : S.convs.get(S.current);
|
|
209
|
+
const current = pick ? pick.current : draft ? (S.draftCwd ?? S.workspaceRoot ?? "") : (conv?.cwd ?? "");
|
|
210
|
+
const goLabel = pick || draft ? "使用此目录" : "切换到此目录";
|
|
207
211
|
let showHidden = localStorage.getItem("aim.cwdHidden") === "1";
|
|
208
212
|
let listing = null;
|
|
209
213
|
let seq = 0;
|
|
@@ -231,9 +235,9 @@ export async function changeCwd() {
|
|
|
231
235
|
list,
|
|
232
236
|
);
|
|
233
237
|
const go = h("button", { class: "btn primary", disabled: true }, goLabel);
|
|
234
|
-
const note = h("span", { class: "muted grow" }, draft ? "发送第一条消息时在这个目录开会话" : "切换后开新会话");
|
|
238
|
+
const note = h("span", { class: "muted grow" }, pick ? "" : draft ? "发送第一条消息时在这个目录开会话" : "切换后开新会话");
|
|
235
239
|
const close = modal({
|
|
236
|
-
title: draft ? "选择工作目录" : "切换工作目录",
|
|
240
|
+
title: pick || draft ? "选择工作目录" : "切换工作目录",
|
|
237
241
|
body,
|
|
238
242
|
foot: h("div", { class: "modal-foot", style: { alignItems: "center" } }, note, go),
|
|
239
243
|
});
|
|
@@ -241,6 +245,10 @@ export async function changeCwd() {
|
|
|
241
245
|
go.onclick = async () => {
|
|
242
246
|
if (!listing) return;
|
|
243
247
|
close();
|
|
248
|
+
if (pick) {
|
|
249
|
+
pick.onPick(listing.path);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
244
252
|
if (draft) {
|
|
245
253
|
S.draftCwd = listing.path === listing.root ? null : listing.path;
|
|
246
254
|
if (!S.current) renderEmpty();
|
package/web/views/layout.js
CHANGED
|
@@ -47,6 +47,22 @@ function openSettings() {
|
|
|
47
47
|
);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/** 技能页:第一次打开时才加载 */
|
|
51
|
+
export function openSkillsView() {
|
|
52
|
+
import("./skills.js").then(
|
|
53
|
+
(m) => m.openSkills(),
|
|
54
|
+
(err) => toast(`技能页加载失败,刷新页面再试:${err.message}`),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 定时任务页:第一次打开时才加载 */
|
|
59
|
+
export function openTasksView() {
|
|
60
|
+
import("./tasks.js").then(
|
|
61
|
+
(m) => m.openTasks(),
|
|
62
|
+
(err) => toast(`定时任务页加载失败,刷新页面再试:${err.message}`),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
50
66
|
export async function boot() {
|
|
51
67
|
const me = await api("/api/me");
|
|
52
68
|
if (!me.authed) return showAuth();
|
|
@@ -72,7 +88,9 @@ export async function boot() {
|
|
|
72
88
|
const fromHash = decodeURIComponent(location.hash.slice(1));
|
|
73
89
|
// 地址里带了会话就打开它;否则打开「最近」里最近活跃的那个对话,一个都没有才进新对话页
|
|
74
90
|
const latest = [...S.convs.values()].filter((c) => !c.hidden).sort((a, b) => b.activeAt - a.activeAt)[0];
|
|
75
|
-
if (fromHash
|
|
91
|
+
if (fromHash === "tasks") openTasksView();
|
|
92
|
+
else if (fromHash === "skills") openSkillsView();
|
|
93
|
+
else if (fromHash && S.convs.has(fromHash)) await openConversation(fromHash);
|
|
76
94
|
else if (latest) await openConversation(latest.key);
|
|
77
95
|
else newDraft();
|
|
78
96
|
// 点了桌面端的系统通知 / 菜单栏里的待处理项:跳到对应会话
|
|
@@ -159,6 +177,9 @@ function renderLayout() {
|
|
|
159
177
|
{ class: "side-top" },
|
|
160
178
|
h("button", { class: "btn new-chat", onclick: () => (newDraft(), closeSide()) }, icon("compose"), "新聊天"),
|
|
161
179
|
),
|
|
180
|
+
// 「最近」上面的入口,像 ChatGPT 的 Tasks。当前在任务页时高亮(renderSidebar 里切换)
|
|
181
|
+
(ui.tasksNav = h("button", { class: "btn new-chat side-nav", onclick: () => openTasksView() }, icon("clock"), "定时任务")),
|
|
182
|
+
(ui.skillsNav = h("button", { class: "btn new-chat side-nav", onclick: () => openSkillsView() }, icon("spark"), "技能")),
|
|
162
183
|
ui.convList,
|
|
163
184
|
h(
|
|
164
185
|
"div",
|