agent-loft 0.2.28 → 0.2.30

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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/src/channels/web/server.ts +1 -1
  3. package/web/app.js +8 -5555
  4. package/web/css/base.css +291 -0
  5. package/web/css/composer.css +506 -0
  6. package/web/css/loader.css +196 -0
  7. package/web/css/messages.css +734 -0
  8. package/web/css/mobile.css +100 -0
  9. package/web/css/overlay.css +437 -0
  10. package/web/css/schedule.css +93 -0
  11. package/web/css/settings.css +335 -0
  12. package/web/css/sidebar.css +593 -0
  13. package/web/css/topbar.css +205 -0
  14. package/web/index.html +11 -1
  15. package/web/lib/api.js +26 -0
  16. package/web/lib/dom.js +101 -0
  17. package/web/lib/markdown.js +52 -0
  18. package/web/lib/overlay.js +144 -0
  19. package/web/lib/push.js +70 -0
  20. package/web/lib/time.js +60 -0
  21. package/web/lib/ws.js +232 -0
  22. package/web/settings/agents.js +210 -0
  23. package/web/settings/commands.js +411 -0
  24. package/web/settings/common.js +42 -0
  25. package/web/settings/feishu.js +181 -0
  26. package/web/settings/general.js +184 -0
  27. package/web/settings/index.js +63 -0
  28. package/web/settings/memory.js +366 -0
  29. package/web/settings/notify.js +80 -0
  30. package/web/settings/security.js +188 -0
  31. package/web/settings/users.js +189 -0
  32. package/web/state.js +44 -0
  33. package/web/views/auth.js +61 -0
  34. package/web/views/composer.js +324 -0
  35. package/web/views/conv-actions.js +404 -0
  36. package/web/views/interaction.js +140 -0
  37. package/web/views/layout.js +186 -0
  38. package/web/views/messages.js +510 -0
  39. package/web/views/quota.js +145 -0
  40. package/web/views/schedule.js +120 -0
  41. package/web/views/sidebar.js +455 -0
  42. package/web/views/slash.js +185 -0
  43. package/web/views/topbar.js +394 -0
  44. package/web/views/voice.js +189 -0
  45. package/web/style.css +0 -3500
@@ -0,0 +1,506 @@
1
+ /* ---------- 输入框 ---------- */
2
+
3
+ .composer-wrap {
4
+ padding: 0 16px calc(12px + env(safe-area-inset-bottom));
5
+ position: relative;
6
+ z-index: 2;
7
+ }
8
+
9
+ /* 输入框上沿的渐变:聊天内容滚到底部时柔和地淡出,不是被一刀切开(和顶栏下沿对称)。
10
+ 右边让出滚动条的宽度,免得把滚动条也盖淡 */
11
+ .composer-wrap::before {
12
+ content: "";
13
+ position: absolute;
14
+ left: 0;
15
+ right: 10px;
16
+ bottom: 100%;
17
+ height: 32px;
18
+ pointer-events: none;
19
+ background: linear-gradient(
20
+ to top,
21
+ var(--bg),
22
+ color-mix(in srgb, var(--bg) 70%, transparent) 40%,
23
+ color-mix(in srgb, var(--bg) 0%, transparent)
24
+ );
25
+ }
26
+
27
+ .composer-box {
28
+ max-width: 820px;
29
+ margin: 0 auto;
30
+ }
31
+
32
+ /* 输入框上方的灰色上下文条,下沿藏在输入框后面 */
33
+ .ctx-bar {
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 6px;
37
+ margin: 0 14px -22px;
38
+ padding: 5px 10px 27px;
39
+ background: var(--bg-soft);
40
+ border-radius: 16px 16px 0 0;
41
+ font-size: 14px;
42
+ color: var(--fg);
43
+ }
44
+
45
+ .ctx-bar .spacer {
46
+ flex: 1;
47
+ }
48
+
49
+ .ctx-item {
50
+ display: inline-flex;
51
+ align-items: center;
52
+ gap: 8px;
53
+ min-width: 0;
54
+ height: 30px;
55
+ padding: 0 8px;
56
+ border-radius: 999px;
57
+ color: inherit;
58
+ font: inherit;
59
+ }
60
+
61
+ .ctx-item .label {
62
+ overflow: hidden;
63
+ text-overflow: ellipsis;
64
+ white-space: nowrap;
65
+ }
66
+
67
+ button.ctx-item:hover {
68
+ background: var(--bg-hover);
69
+ }
70
+
71
+ .ctx-item.static {
72
+ flex-shrink: 0;
73
+ }
74
+
75
+ .ctx-item svg {
76
+ width: 17px;
77
+ height: 17px;
78
+ flex-shrink: 0;
79
+ }
80
+
81
+ .ctx-bar[hidden] {
82
+ display: none;
83
+ }
84
+
85
+ .ctx-item .agent-icon {
86
+ display: inline-grid;
87
+ color: var(--fg-soft);
88
+ }
89
+
90
+ .ctx-item .chev {
91
+ display: inline-grid;
92
+ margin-left: -2px;
93
+ }
94
+
95
+ .ctx-item .chev svg {
96
+ width: 13px;
97
+ height: 13px;
98
+ color: var(--fg-faint);
99
+ }
100
+
101
+ .composer {
102
+ background: var(--bg-input);
103
+ border: 1px solid color-mix(in srgb, var(--border) 85%, transparent);
104
+ border-radius: 22px;
105
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03), 0 4px 16px rgba(0, 0, 0, 0.05);
106
+ padding: 0;
107
+ display: flex;
108
+ flex-direction: column;
109
+ gap: 0;
110
+ }
111
+
112
+ .composer .icon-btn {
113
+ width: 32px;
114
+ height: 32px;
115
+ }
116
+
117
+ .composer .icon-btn svg {
118
+ width: 18px;
119
+ height: 18px;
120
+ }
121
+
122
+ .composer .add-btn {
123
+ color: var(--fg);
124
+ }
125
+
126
+ .composer .add-btn svg {
127
+ width: 20px;
128
+ height: 20px;
129
+ }
130
+
131
+ /* 输入框里的按钮 hover 都用圆形 / 胶囊底,和圆形发送按钮一致 */
132
+ .composer .icon-btn {
133
+ border-radius: 50%;
134
+ }
135
+
136
+ .model-pill {
137
+ display: inline-flex;
138
+ align-items: center;
139
+ gap: 5px;
140
+ max-width: min(260px, 45vw);
141
+ height: 32px;
142
+ padding: 0 10px;
143
+ border-radius: 999px;
144
+ font-size: 14px;
145
+ color: var(--fg);
146
+ white-space: nowrap;
147
+ }
148
+
149
+ .model-pill:hover {
150
+ background: var(--bg-hover);
151
+ }
152
+
153
+ .model-pill .model-name {
154
+ overflow: hidden;
155
+ text-overflow: ellipsis;
156
+ }
157
+
158
+ .model-pill .sub {
159
+ color: var(--fg-faint);
160
+ flex-shrink: 0;
161
+ }
162
+
163
+ .model-pill svg {
164
+ width: 13px;
165
+ height: 13px;
166
+ color: var(--fg-faint);
167
+ flex-shrink: 0;
168
+ }
169
+
170
+ .mode-pill {
171
+ display: inline-flex;
172
+ align-items: center;
173
+ height: 32px;
174
+ padding: 0 10px;
175
+ border-radius: 999px;
176
+ font-size: 13px;
177
+ color: var(--fg-faint);
178
+ white-space: nowrap;
179
+ }
180
+
181
+ .mode-pill:hover {
182
+ background: var(--bg-hover);
183
+ color: var(--fg);
184
+ }
185
+
186
+ .mode-pill.chat {
187
+ color: var(--accent);
188
+ }
189
+
190
+ .topbar .mode-slot {
191
+ display: inline-flex;
192
+ flex-shrink: 0;
193
+ }
194
+
195
+ .topbar .top-more svg {
196
+ width: 20px;
197
+ height: 20px;
198
+ }
199
+
200
+ .composer.drag {
201
+ border-color: var(--accent);
202
+ }
203
+
204
+ .composer textarea {
205
+ border: none;
206
+ outline: none;
207
+ background: transparent;
208
+ resize: none;
209
+ padding: 14px 16px 4px;
210
+ max-height: 40vh;
211
+ min-height: 50px;
212
+ line-height: 1.5;
213
+ font-size: 16px; /* iOS 小于 16px 会自动放大页面 */
214
+ }
215
+
216
+ .composer textarea::placeholder {
217
+ color: color-mix(in srgb, var(--fg-faint) 70%, transparent);
218
+ }
219
+
220
+ .composer .chips:not(:empty) {
221
+ padding: 10px 12px 0;
222
+ }
223
+
224
+ .composer-row {
225
+ display: flex;
226
+ align-items: center;
227
+ gap: 2px;
228
+ padding: 4px 10px 10px 8px;
229
+ }
230
+
231
+ .composer-row .spacer {
232
+ flex: 1;
233
+ }
234
+
235
+ /* 上下文窗口圆环 + hover 气泡 */
236
+ .ctx-ring-slot {
237
+ display: inline-flex;
238
+ }
239
+
240
+ /* 只是个指示器,不可点击:不加 hover 底色 */
241
+ .ctx-ring {
242
+ position: relative;
243
+ display: inline-grid;
244
+ place-items: center;
245
+ width: 22px;
246
+ height: 32px;
247
+ margin-right: 2px;
248
+ cursor: default;
249
+ outline: none;
250
+ }
251
+
252
+ /* 视觉修正:旁边模型名是小写英文,字形重心比行框中心低 1px 多(macOS 的 SF 字体尤其明显),
253
+ 圆环按几何居中会显得偏高,往下挪 1px 对齐文字 */
254
+ .ctx-ring .ring {
255
+ display: inline-grid;
256
+ transform: translateY(1px);
257
+ }
258
+
259
+ .ctx-ring svg {
260
+ width: 13px;
261
+ height: 13px;
262
+ }
263
+
264
+ .ctx-ring .track {
265
+ fill: none;
266
+ stroke: color-mix(in srgb, var(--fg-faint) 28%, transparent);
267
+ stroke-width: 2.6;
268
+ }
269
+
270
+ .ctx-ring .bar {
271
+ fill: none;
272
+ stroke: var(--fg-faint);
273
+ stroke-width: 2.6;
274
+ stroke-linecap: round;
275
+ }
276
+
277
+ .ctx-ring.warn .bar {
278
+ stroke: #f59f00;
279
+ }
280
+
281
+ .ctx-ring.danger .bar {
282
+ stroke: var(--danger);
283
+ }
284
+
285
+ .ctx-ring .tip {
286
+ position: absolute;
287
+ bottom: calc(100% + 10px);
288
+ left: 50%;
289
+ transform: translate(-50%, 4px);
290
+ display: flex;
291
+ flex-direction: column;
292
+ align-items: center;
293
+ gap: 2px;
294
+ padding: 10px 16px;
295
+ border-radius: 12px;
296
+ background: #1f2023;
297
+ color: #f2f2f2;
298
+ font-size: 13px;
299
+ line-height: 1.5;
300
+ white-space: nowrap;
301
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
302
+ opacity: 0;
303
+ visibility: hidden;
304
+ transition: opacity 0.12s, transform 0.12s, visibility 0.12s;
305
+ pointer-events: none;
306
+ z-index: 30;
307
+ }
308
+
309
+ .ctx-ring .tip-title {
310
+ color: #a8a8a8;
311
+ }
312
+
313
+ .ctx-ring:hover .tip,
314
+ .ctx-ring:focus-visible .tip {
315
+ opacity: 1;
316
+ visibility: visible;
317
+ transform: translate(-50%, 0);
318
+ }
319
+
320
+ .model-slot {
321
+ display: inline-flex;
322
+ min-width: 0;
323
+ }
324
+
325
+ .mic-btn.recording {
326
+ color: var(--danger);
327
+ background: color-mix(in srgb, var(--danger) 12%, transparent);
328
+ animation: pulse 1.2s infinite;
329
+ }
330
+
331
+ .send-btn {
332
+ width: 30px;
333
+ height: 30px;
334
+ flex-shrink: 0;
335
+ border-radius: 50%;
336
+ /* 像 GPT 那样:浅色模式黑底白箭头,深色模式反过来 */
337
+ background: var(--fg);
338
+ color: var(--bg);
339
+ display: grid;
340
+ place-items: center;
341
+ margin-left: 6px;
342
+ }
343
+
344
+ .send-btn:disabled {
345
+ opacity: 0.4;
346
+ }
347
+
348
+ .send-btn:has(.send-spinner) {
349
+ opacity: 1;
350
+ }
351
+
352
+ /* 发送中:按钮里转圈,颜色跟着按钮的前景色走(深浅模式都看得清) */
353
+ .send-spinner {
354
+ width: 14px;
355
+ height: 14px;
356
+ border: 2px solid currentColor;
357
+ border-right-color: transparent;
358
+ border-radius: 50%;
359
+ animation: spin 0.8s linear infinite;
360
+ }
361
+
362
+ textarea.sending {
363
+ opacity: 0.6;
364
+ }
365
+
366
+ .send-btn svg {
367
+ width: 15px;
368
+ height: 15px;
369
+ }
370
+
371
+ .chips {
372
+ display: flex;
373
+ flex-wrap: wrap;
374
+ gap: 6px;
375
+ padding: 0 4px;
376
+ }
377
+
378
+ .chip {
379
+ display: inline-flex;
380
+ align-items: center;
381
+ gap: 6px;
382
+ background: var(--bg-soft);
383
+ border-radius: 8px;
384
+ padding: 4px 8px;
385
+ font-size: 13px;
386
+ max-width: 220px;
387
+ }
388
+
389
+ .chip span {
390
+ overflow: hidden;
391
+ text-overflow: ellipsis;
392
+ white-space: nowrap;
393
+ }
394
+
395
+ .chip button {
396
+ color: var(--fg-faint);
397
+ }
398
+
399
+ .composer-hint {
400
+ text-align: center;
401
+ font-size: 12px;
402
+ color: var(--fg-faint);
403
+ margin-top: 6px;
404
+ }
405
+
406
+ .conn-banner {
407
+ position: absolute;
408
+ top: calc(56px + env(safe-area-inset-top));
409
+ left: 50%;
410
+ transform: translateX(-50%);
411
+ background: var(--fg);
412
+ color: var(--bg);
413
+ padding: 4px 12px;
414
+ border-radius: 999px;
415
+ font-size: 12px;
416
+ z-index: 5;
417
+ }
418
+
419
+ .conn-banner.clickable {
420
+ border: 0;
421
+ font: inherit;
422
+ font-size: 12px;
423
+ cursor: pointer;
424
+ }
425
+
426
+ /* ---------- 斜杠命令补全 ---------- */
427
+
428
+ .composer {
429
+ position: relative;
430
+ }
431
+
432
+ .slash-menu {
433
+ position: absolute;
434
+ left: 0;
435
+ right: 0;
436
+ bottom: calc(100% + 8px);
437
+ max-height: min(360px, 50vh);
438
+ overflow-y: auto;
439
+ background: var(--bg-input);
440
+ border: 1px solid var(--border);
441
+ border-radius: var(--radius);
442
+ box-shadow: var(--shadow);
443
+ padding: 6px;
444
+ z-index: 20;
445
+ }
446
+
447
+ .slash-menu[hidden] {
448
+ display: none;
449
+ }
450
+
451
+ .slash-item {
452
+ display: block;
453
+ width: 100%;
454
+ text-align: left;
455
+ padding: 8px 10px;
456
+ border-radius: 8px;
457
+ }
458
+
459
+ .slash-item.on {
460
+ background: var(--bg-hover);
461
+ }
462
+
463
+ .slash-head {
464
+ display: flex;
465
+ align-items: baseline;
466
+ gap: 8px;
467
+ min-width: 0;
468
+ }
469
+
470
+ .slash-name {
471
+ font-family: var(--mono);
472
+ font-weight: 600;
473
+ }
474
+
475
+ .slash-args {
476
+ font-family: var(--mono);
477
+ font-size: 12px;
478
+ color: var(--fg-soft);
479
+ white-space: nowrap;
480
+ overflow: hidden;
481
+ text-overflow: ellipsis;
482
+ }
483
+
484
+ .slash-desc {
485
+ font-size: 12px;
486
+ color: var(--fg-faint);
487
+ overflow: hidden;
488
+ text-overflow: ellipsis;
489
+ white-space: nowrap;
490
+ }
491
+
492
+ .slash-empty {
493
+ padding: 10px;
494
+ color: var(--fg-faint);
495
+ font-size: 13px;
496
+ }
497
+
498
+ .slash-item .badge,
499
+ .cmd-row .badge {
500
+ font-size: 11px;
501
+ padding: 1px 6px;
502
+ border-radius: 6px;
503
+ background: var(--bg-soft);
504
+ color: var(--fg-soft);
505
+ margin-left: 6px;
506
+ }
@@ -0,0 +1,196 @@
1
+ /* ---------- logo 加载动画:三颗星依次跳起,台面跟着呼吸 ---------- */
2
+ .loft-loader {
3
+ --size: 26px;
4
+ display: inline-block;
5
+ flex-shrink: 0;
6
+ width: var(--size);
7
+ height: var(--size);
8
+ line-height: 0;
9
+ }
10
+
11
+ .loft-loader.xl {
12
+ --size: 72px;
13
+ }
14
+
15
+ .loft-loader svg {
16
+ display: block;
17
+ width: 100%;
18
+ height: 100%;
19
+ overflow: visible;
20
+ }
21
+
22
+ .loft-loader path {
23
+ transform-box: fill-box;
24
+ transform-origin: 50% 50%;
25
+ animation-duration: 1.6s;
26
+ animation-iteration-count: infinite;
27
+ animation-timing-function: cubic-bezier(0.45, 0, 0.25, 1);
28
+ animation-fill-mode: both;
29
+ animation-delay: calc(var(--t, 0ms) + var(--d, 0ms));
30
+ }
31
+
32
+ .loft-loader .ll-star {
33
+ animation-name: loft-star;
34
+ }
35
+
36
+ .loft-loader .ll-l {
37
+ --d: 0ms;
38
+ }
39
+
40
+ .loft-loader .ll-c {
41
+ --d: 180ms;
42
+ }
43
+
44
+ .loft-loader .ll-r {
45
+ --d: 360ms;
46
+ }
47
+
48
+ .loft-loader .ll-plate {
49
+ animation-name: loft-plate;
50
+ --d: 260ms;
51
+ }
52
+
53
+ .loft-loader .ll-shadow {
54
+ animation-name: loft-shadow;
55
+ --d: 320ms;
56
+ }
57
+
58
+ @keyframes loft-star {
59
+ 0%,
60
+ 70%,
61
+ 100% {
62
+ transform: translateY(0) scale(0.72);
63
+ opacity: 0.4;
64
+ }
65
+ 28% {
66
+ transform: translateY(-22px) scale(1.06);
67
+ opacity: 1;
68
+ }
69
+ 48% {
70
+ transform: translateY(0) scale(0.92);
71
+ opacity: 0.85;
72
+ }
73
+ }
74
+
75
+ @keyframes loft-plate {
76
+ 0%,
77
+ 100% {
78
+ opacity: 0.75;
79
+ }
80
+ 45% {
81
+ opacity: 1;
82
+ }
83
+ }
84
+
85
+ @keyframes loft-shadow {
86
+ 0%,
87
+ 100% {
88
+ transform: translateY(-6px);
89
+ opacity: 0.32;
90
+ }
91
+ 50% {
92
+ transform: translateY(6px);
93
+ opacity: 0.12;
94
+ }
95
+ }
96
+
97
+ @media (prefers-reduced-motion: reduce) {
98
+ .loft-loader .ll-star {
99
+ animation-name: loft-star-calm;
100
+ animation-duration: 2.4s;
101
+ }
102
+ .loft-loader .ll-shadow {
103
+ animation: none;
104
+ opacity: 0.3;
105
+ }
106
+ }
107
+
108
+ /* 回复末尾的精简版:只有一颗星,旋转 + 放大缩小 */
109
+ .loft-stars {
110
+ display: inline-block;
111
+ flex-shrink: 0;
112
+ width: 18px;
113
+ height: 18px;
114
+ line-height: 0;
115
+ }
116
+
117
+ /* 打开会话、读历史时用的大一号 */
118
+ .loft-stars.lg {
119
+ width: 32px;
120
+ height: 32px;
121
+ }
122
+
123
+ .loft-stars svg {
124
+ display: block;
125
+ width: 100%;
126
+ height: 100%;
127
+ overflow: visible;
128
+ }
129
+
130
+ .loft-stars path {
131
+ transform-box: fill-box;
132
+ transform-origin: 50% 50%;
133
+ animation: loft-twirl 1.8s cubic-bezier(0.45, 0, 0.25, 1) infinite both;
134
+ animation-delay: var(--t, 0ms);
135
+ }
136
+
137
+ /* 四角星转 90° 正好回到原样,所以每轮转 180° 能无缝接上 */
138
+ @keyframes loft-twirl {
139
+ 0% {
140
+ transform: rotate(0deg) scale(0.6);
141
+ opacity: 0.55;
142
+ }
143
+ 45% {
144
+ transform: rotate(110deg) scale(1.12);
145
+ opacity: 1;
146
+ }
147
+ 100% {
148
+ transform: rotate(180deg) scale(0.6);
149
+ opacity: 0.55;
150
+ }
151
+ }
152
+
153
+ @media (prefers-reduced-motion: reduce) {
154
+ .loft-stars path {
155
+ animation-name: loft-star-calm;
156
+ animation-duration: 2.4s;
157
+ }
158
+ }
159
+
160
+ @keyframes loft-star-calm {
161
+ 0%,
162
+ 100% {
163
+ opacity: 0.4;
164
+ }
165
+ 40% {
166
+ opacity: 1;
167
+ }
168
+ }
169
+
170
+ .footer-note {
171
+ font-size: 12px;
172
+ color: var(--fg-faint);
173
+ }
174
+
175
+ .notice {
176
+ align-self: center;
177
+ font-size: 13px;
178
+ color: var(--fg-soft);
179
+ background: var(--bg-soft);
180
+ padding: 6px 14px;
181
+ border-radius: 12px;
182
+ max-width: 90%;
183
+ }
184
+
185
+ .notice .md p {
186
+ margin: 0;
187
+ }
188
+
189
+ .error-box {
190
+ border: 1px solid var(--danger);
191
+ color: var(--danger);
192
+ border-radius: 10px;
193
+ padding: 8px 12px;
194
+ font-size: 14px;
195
+ white-space: pre-wrap;
196
+ }