psyclaw 0.29.11 → 0.29.13

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 (60) hide show
  1. package/README.md +4 -4
  2. package/dist/apps/panel/index.html +849 -337
  3. package/dist/src/adapters/pi/extension.js +87 -15
  4. package/dist/src/adapters/pi/extension.js.map +1 -1
  5. package/dist/src/adapters/pi/rpc.js +1 -1
  6. package/dist/src/adapters/pi/rpc.js.map +1 -1
  7. package/dist/src/analysis/stats-router.js +1 -1
  8. package/dist/src/analysis/stats-router.js.map +1 -1
  9. package/dist/src/ars/mode-editor.d.ts +1 -5
  10. package/dist/src/ars/mode-editor.js +1 -9
  11. package/dist/src/ars/mode-editor.js.map +1 -1
  12. package/dist/src/branding.d.ts +4 -3
  13. package/dist/src/branding.js +10 -9
  14. package/dist/src/branding.js.map +1 -1
  15. package/dist/src/chat.d.ts +7 -0
  16. package/dist/src/chat.js +19 -1
  17. package/dist/src/chat.js.map +1 -1
  18. package/dist/src/cli-args.d.ts +1 -0
  19. package/dist/src/cli-args.js +1 -0
  20. package/dist/src/cli-args.js.map +1 -1
  21. package/dist/src/cli.js +39 -7
  22. package/dist/src/cli.js.map +1 -1
  23. package/dist/src/index.d.ts +2 -0
  24. package/dist/src/index.js +2 -0
  25. package/dist/src/index.js.map +1 -1
  26. package/dist/src/literature/knowledge-map.d.ts +4 -0
  27. package/dist/src/literature/knowledge-map.js +4 -0
  28. package/dist/src/literature/knowledge-map.js.map +1 -1
  29. package/dist/src/panel/server.js +9 -109
  30. package/dist/src/panel/server.js.map +1 -1
  31. package/dist/src/project/bootstrap.js +0 -2
  32. package/dist/src/project/bootstrap.js.map +1 -1
  33. package/dist/src/project/workspace.js +3 -3
  34. package/dist/src/project/workspace.js.map +1 -1
  35. package/dist/src/research/choice.d.ts +16 -0
  36. package/dist/src/research/choice.js +25 -0
  37. package/dist/src/research/choice.js.map +1 -0
  38. package/dist/src/session/continuously-work.d.ts +16 -0
  39. package/dist/src/session/continuously-work.js +44 -0
  40. package/dist/src/session/continuously-work.js.map +1 -0
  41. package/dist/src/session/help.d.ts +3 -0
  42. package/dist/src/session/help.js +68 -26
  43. package/dist/src/session/help.js.map +1 -1
  44. package/dist/src/session/modes.js +7 -7
  45. package/dist/src/session/modes.js.map +1 -1
  46. package/dist/src/style/cli-ui.d.ts +1 -0
  47. package/dist/src/style/cli-ui.js +26 -14
  48. package/dist/src/style/cli-ui.js.map +1 -1
  49. package/dist/src/updates/update.d.ts +8 -1
  50. package/dist/src/updates/update.js +68 -2
  51. package/dist/src/updates/update.js.map +1 -1
  52. package/dist/src/verify/checklist.d.ts +46 -5
  53. package/dist/src/verify/checklist.js +139 -24
  54. package/dist/src/verify/checklist.js.map +1 -1
  55. package/dist/src/wake-options/runtime.d.ts +1 -0
  56. package/dist/src/wake-options/runtime.js +12 -2
  57. package/dist/src/wake-options/runtime.js.map +1 -1
  58. package/package.json +1 -1
  59. package/skills/recommended/catalog.json +24 -154
  60. package/skills/recommended/mcp-catalog.json +16 -4
@@ -160,20 +160,29 @@
160
160
  display: grid;
161
161
  grid-template-columns: 210px minmax(0, 1fr);
162
162
  min-height: 0;
163
+ transition: grid-template-columns 0.2s cubic-bezier(0.4, 0, 0.2, 1);
164
+ }
165
+ .layout.nav-collapsed {
166
+ grid-template-columns: 58px minmax(0, 1fr);
163
167
  }
164
168
 
165
169
  .nav {
166
- padding: 16px 10px;
170
+ padding: 14px 8px;
167
171
  background: var(--bg-sidebar);
168
172
  border-right: 1px solid var(--border-subtle);
169
173
  display: flex;
170
174
  flex-direction: column;
171
175
  justify-content: space-between;
176
+ overflow: hidden;
177
+ transition: padding 0.2s ease;
178
+ position: relative;
172
179
  }
173
- .nav-group {
180
+ .nav-header {
174
181
  display: flex;
175
- flex-direction: column;
176
- gap: 3px;
182
+ align-items: center;
183
+ justify-content: space-between;
184
+ padding: 4px 6px 8px;
185
+ margin-bottom: 4px;
177
186
  }
178
187
  .nav-label {
179
188
  font-size: 10.5px;
@@ -181,26 +190,67 @@
181
190
  color: var(--text-tertiary);
182
191
  letter-spacing: 0.05em;
183
192
  text-transform: uppercase;
184
- padding: 6px 10px 4px;
185
- margin-bottom: 2px;
193
+ white-space: nowrap;
194
+ overflow: hidden;
195
+ text-overflow: ellipsis;
196
+ transition: opacity 0.15s ease;
197
+ }
198
+ .nav-toggle-btn {
199
+ width: 26px;
200
+ height: 26px;
201
+ display: grid;
202
+ place-items: center;
203
+ border-radius: var(--radius-sm);
204
+ color: var(--text-muted);
205
+ background: transparent;
206
+ cursor: pointer;
207
+ transition: all 0.15s ease;
208
+ flex-shrink: 0;
209
+ }
210
+ .nav-toggle-btn:hover {
211
+ background: var(--bg-surface-subtle);
212
+ color: var(--text-main);
213
+ }
214
+ .layout.nav-collapsed .nav-header {
215
+ justify-content: center;
216
+ padding: 2px 0 10px;
217
+ }
218
+ .layout.nav-collapsed .nav-label {
219
+ display: none;
220
+ }
221
+ .layout.nav-collapsed .nav-toggle-btn svg {
222
+ transform: rotate(180deg);
223
+ }
224
+
225
+ .nav-group {
226
+ display: flex;
227
+ flex-direction: column;
228
+ gap: 4px;
186
229
  }
187
230
  .nav button {
188
231
  width: 100%;
189
232
  display: flex;
190
233
  align-items: center;
191
- gap: 9px;
192
- padding: 8px 10px;
234
+ gap: 10px;
235
+ padding: 9px 10px;
193
236
  border-radius: var(--radius-sm);
194
237
  color: var(--text-secondary);
195
238
  font-weight: 500;
196
239
  font-size: 12.5px;
197
240
  text-align: left;
198
- transition: all 0.15s ease;
241
+ transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
199
242
  position: relative;
243
+ white-space: nowrap;
244
+ }
245
+ .nav button .nav-btn-text {
246
+ flex: 1;
247
+ overflow: hidden;
248
+ text-overflow: ellipsis;
249
+ transition: opacity 0.15s ease;
200
250
  }
201
251
  .nav button svg {
202
- width: 16px;
203
- height: 16px;
252
+ width: 17px;
253
+ height: 17px;
204
254
  color: var(--text-muted);
205
255
  flex-shrink: 0;
206
256
  transition: color 0.15s ease;
@@ -222,12 +272,29 @@
222
272
  .nav button.active svg {
223
273
  color: var(--brand-primary);
224
274
  }
275
+
276
+ /* Collapsed Nav Styles */
277
+ .layout.nav-collapsed .nav {
278
+ padding: 12px 6px;
279
+ }
280
+ .layout.nav-collapsed .nav button {
281
+ justify-content: center;
282
+ padding: 9px 0;
283
+ }
284
+ .layout.nav-collapsed .nav button .nav-btn-text {
285
+ display: none;
286
+ }
287
+ .layout.nav-collapsed .nav-footer {
288
+ display: none;
289
+ }
290
+
225
291
  .nav-footer {
226
292
  padding: 12px 10px;
227
293
  border-top: 1px solid var(--border-subtle);
228
294
  font-size: 11px;
229
295
  color: var(--text-muted);
230
296
  line-height: 1.4;
297
+ transition: opacity 0.15s ease;
231
298
  }
232
299
  .nav-footer code {
233
300
  font-family: var(--font-mono);
@@ -270,23 +337,41 @@
270
337
  .help-grid {
271
338
  display: grid;
272
339
  grid-template-columns: repeat(3, minmax(0, 1fr));
273
- gap: 12px;
274
- margin-bottom: 22px;
340
+ gap: 14px;
341
+ margin-bottom: 28px;
275
342
  }
276
343
  .help-mode-card {
344
+ position: relative;
277
345
  border: 1px solid var(--border-subtle);
278
- background: var(--bg-surface);
279
- border-radius: var(--radius-md);
280
- padding: 14px 16px;
281
- box-shadow: var(--shadow-xs);
346
+ background: linear-gradient(165deg, #ffffff 0%, var(--brand-primary-light) 140%);
347
+ border-radius: 14px;
348
+ padding: 18px 18px 16px;
349
+ overflow: hidden;
350
+ }
351
+ .help-mode-card::before {
352
+ content: "";
353
+ position: absolute;
354
+ left: 0;
355
+ top: 0;
356
+ bottom: 0;
357
+ width: 3px;
358
+ background: var(--brand-primary);
282
359
  }
283
360
  .help-mode-card .mode-id {
284
361
  font-family: var(--font-mono);
285
- font-size: 11px;
286
- color: var(--accent);
287
- letter-spacing: 0.04em;
362
+ font-size: 10px;
363
+ font-weight: 600;
364
+ color: var(--brand-primary);
365
+ letter-spacing: 0.08em;
288
366
  text-transform: uppercase;
289
- margin-bottom: 6px;
367
+ margin-bottom: 8px;
368
+ }
369
+ .help-mode-card .mode-label {
370
+ font-size: 16px;
371
+ font-weight: 650;
372
+ color: var(--text-main);
373
+ letter-spacing: -0.02em;
374
+ margin: 0 0 8px;
290
375
  }
291
376
  .help-mode-card p {
292
377
  margin: 0;
@@ -294,56 +379,164 @@
294
379
  line-height: 1.55;
295
380
  color: var(--text-secondary);
296
381
  }
297
- .help-block {
382
+ .help-hero {
383
+ display: flex;
384
+ align-items: flex-end;
385
+ justify-content: space-between;
386
+ gap: 16px;
298
387
  margin-bottom: 22px;
388
+ padding: 20px 22px;
389
+ border-radius: 16px;
390
+ border: 1px solid var(--brand-primary-border);
391
+ background:
392
+ radial-gradient(120% 80% at 100% 0%, rgba(13, 118, 110, 0.10), transparent 55%),
393
+ linear-gradient(180deg, #ffffff, #f8fafc);
299
394
  }
300
- .help-block h2 {
301
- margin: 0 0 10px;
302
- font-size: 14px;
303
- font-weight: 650;
395
+ .help-hero h1 {
396
+ margin: 0 0 6px;
397
+ font-size: 22px;
398
+ font-weight: 700;
399
+ letter-spacing: -0.03em;
304
400
  color: var(--text-main);
305
401
  }
402
+ .help-hero p {
403
+ margin: 0;
404
+ color: var(--text-muted);
405
+ font-size: 13px;
406
+ max-width: 42rem;
407
+ line-height: 1.5;
408
+ }
409
+ .help-kbd {
410
+ display: inline-flex;
411
+ align-items: center;
412
+ gap: 6px;
413
+ padding: 6px 10px;
414
+ border-radius: 999px;
415
+ border: 1px solid var(--border-subtle);
416
+ background: var(--bg-surface);
417
+ color: var(--text-secondary);
418
+ font-size: 11px;
419
+ font-family: var(--font-mono);
420
+ white-space: nowrap;
421
+ }
422
+ .help-block {
423
+ margin-bottom: 26px;
424
+ }
425
+ .help-block h2 {
426
+ margin: 0 0 12px;
427
+ font-size: 13px;
428
+ font-weight: 700;
429
+ letter-spacing: 0.04em;
430
+ text-transform: uppercase;
431
+ color: var(--text-muted);
432
+ }
306
433
  .help-steps {
307
434
  margin: 0;
308
- padding-left: 1.2rem;
435
+ padding: 0;
436
+ list-style: none;
437
+ display: grid;
438
+ gap: 8px;
439
+ }
440
+ .help-steps li {
441
+ display: grid;
442
+ grid-template-columns: 28px 1fr;
443
+ gap: 12px;
444
+ align-items: start;
445
+ padding: 12px 14px;
446
+ border-radius: 12px;
447
+ border: 1px solid var(--border-subtle);
448
+ background: var(--bg-surface);
309
449
  color: var(--text-secondary);
310
450
  font-size: 13px;
311
- line-height: 1.7;
451
+ line-height: 1.55;
452
+ }
453
+ .help-steps li::before {
454
+ content: attr(data-step);
455
+ width: 28px;
456
+ height: 28px;
457
+ border-radius: 9px;
458
+ display: grid;
459
+ place-items: center;
460
+ background: var(--brand-primary-light);
461
+ color: var(--brand-primary);
462
+ font-family: var(--font-mono);
463
+ font-size: 12px;
464
+ font-weight: 700;
312
465
  }
313
466
  .help-commands {
467
+ display: grid;
468
+ gap: 18px;
469
+ }
470
+ .help-cmd-group h3 {
471
+ margin: 0 0 8px;
472
+ font-size: 12px;
473
+ font-weight: 650;
474
+ color: var(--text-muted);
475
+ letter-spacing: 0.02em;
476
+ }
477
+ .help-cmd-list {
314
478
  display: grid;
315
479
  grid-template-columns: repeat(2, minmax(0, 1fr));
316
480
  gap: 8px;
317
481
  }
318
482
  .help-cmd {
319
- display: flex;
320
- gap: 10px;
321
- align-items: baseline;
483
+ display: grid;
484
+ grid-template-columns: minmax(7.5rem, auto) 1fr;
485
+ gap: 12px;
486
+ align-items: center;
322
487
  border: 1px solid var(--border-subtle);
323
- border-radius: var(--radius-sm);
324
- padding: 10px 12px;
325
- background: var(--bg-surface-subtle);
488
+ border-radius: 12px;
489
+ padding: 11px 13px;
490
+ background: var(--bg-surface);
491
+ transition: border-color 120ms ease, background 120ms ease;
492
+ }
493
+ .help-cmd:hover {
494
+ border-color: var(--brand-primary-border);
495
+ background: var(--brand-primary-light);
326
496
  }
327
497
  .help-cmd code {
328
498
  font-family: var(--font-mono);
329
499
  font-size: 12px;
330
- color: var(--text-main);
500
+ font-weight: 600;
501
+ color: var(--brand-primary);
331
502
  white-space: nowrap;
332
503
  }
333
504
  .help-cmd span {
334
505
  font-size: 12.5px;
335
- color: var(--text-muted);
336
- line-height: 1.45;
506
+ color: var(--text-secondary);
507
+ line-height: 1.4;
337
508
  }
338
509
  .help-notes {
339
510
  margin: 0;
340
- padding-left: 1.2rem;
511
+ padding: 14px 16px;
512
+ list-style: none;
513
+ display: grid;
514
+ gap: 8px;
515
+ border-radius: 14px;
516
+ border: 1px solid var(--border-subtle);
517
+ background: var(--bg-surface-subtle);
518
+ }
519
+ .help-notes li {
520
+ position: relative;
521
+ padding-left: 14px;
341
522
  color: var(--text-muted);
342
523
  font-size: 12.5px;
343
- line-height: 1.65;
524
+ line-height: 1.55;
525
+ }
526
+ .help-notes li::before {
527
+ content: "";
528
+ position: absolute;
529
+ left: 0;
530
+ top: 0.55em;
531
+ width: 6px;
532
+ height: 6px;
533
+ border-radius: 50%;
534
+ background: var(--brand-primary);
535
+ opacity: 0.55;
344
536
  }
345
537
  @media (max-width: 960px) {
346
- .help-grid, .help-commands { grid-template-columns: 1fr; }
538
+ .help-grid, .help-cmd-list { grid-template-columns: 1fr; }
539
+ .help-hero { flex-direction: column; align-items: flex-start; }
347
540
  }
348
541
 
349
542
  .button {
@@ -939,234 +1132,424 @@
939
1132
  background: var(--brand-primary-hover);
940
1133
  }
941
1134
 
942
- /* Chat drawer light app chrome (not terminal) */
943
- .console-fab {
944
- position: fixed;
945
- bottom: 24px;
946
- right: 28px;
947
- z-index: 100;
1135
+ /* In-page chat: Modern Codex / Gemini style */
1136
+ .console-drawer {
1137
+ position: relative;
1138
+ width: 100%;
1139
+ height: 100%;
1140
+ min-height: 72vh;
1141
+ flex: 1;
1142
+ background: var(--bg-surface);
1143
+ color: var(--text-main);
1144
+ border: none;
1145
+ display: flex;
1146
+ flex-direction: column;
1147
+ font-family: var(--font-sans);
1148
+ }
1149
+ .console-header {
948
1150
  display: flex;
949
1151
  align-items: center;
950
- gap: 8px;
951
- padding: 10px 18px;
1152
+ justify-content: space-between;
1153
+ padding: 12px 20px;
1154
+ border-bottom: 1px solid var(--border-subtle);
952
1155
  background: var(--bg-surface);
953
- color: var(--brand-primary);
954
- border: 1px solid var(--brand-primary-border);
955
- border-radius: var(--radius-lg);
956
- font-size: 13px;
1156
+ }
1157
+ .console-title {
1158
+ font-size: 13.5px;
1159
+ font-weight: 650;
1160
+ color: var(--text-main);
1161
+ display: flex;
1162
+ align-items: center;
1163
+ gap: 9px;
1164
+ }
1165
+ .console-status-tag {
1166
+ font-size: 10.5px;
957
1167
  font-weight: 600;
1168
+ padding: 1px 7px;
1169
+ border-radius: 999px;
1170
+ background: var(--ok-bg);
1171
+ color: var(--ok-text);
1172
+ border: 1px solid var(--ok-border);
1173
+ display: inline-flex;
1174
+ align-items: center;
1175
+ gap: 4px;
1176
+ }
1177
+ .console-actions {
1178
+ display: flex;
1179
+ align-items: center;
1180
+ gap: 8px;
1181
+ }
1182
+ .console-btn-icon {
1183
+ background: transparent;
1184
+ border: none;
1185
+ color: var(--text-muted);
958
1186
  cursor: pointer;
959
- box-shadow: var(--shadow-md);
960
- transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
1187
+ padding: 5px;
1188
+ border-radius: var(--radius-sm);
1189
+ display: flex;
1190
+ align-items: center;
1191
+ justify-content: center;
1192
+ transition: all 0.15s ease;
1193
+ }
1194
+ .console-btn-icon:hover {
1195
+ color: var(--text-main);
1196
+ background: var(--bg-surface-subtle);
961
1197
  }
962
- .console-fab:hover {
1198
+
1199
+ .console-log {
1200
+ flex: 1;
1201
+ overflow-y: auto;
1202
+ padding: 24px 20px;
1203
+ font-family: var(--font-sans);
1204
+ font-size: 13.5px;
1205
+ line-height: 1.6;
1206
+ display: flex;
1207
+ flex-direction: column;
1208
+ gap: 18px;
1209
+ background: var(--bg-app);
1210
+ scroll-behavior: smooth;
1211
+ }
1212
+
1213
+ /* Message Rows */
1214
+ .log-entry {
1215
+ display: flex;
1216
+ width: 100%;
1217
+ max-width: 860px;
1218
+ margin: 0 auto;
1219
+ gap: 12px;
1220
+ animation: fadeIn 0.18s ease;
1221
+ }
1222
+ .log-entry.user {
1223
+ justify-content: flex-end;
1224
+ }
1225
+ .log-entry.agent,
1226
+ .log-entry.sys {
1227
+ justify-content: flex-start;
1228
+ }
1229
+
1230
+ /* User Message Bubble */
1231
+ .log-user-wrap {
1232
+ max-width: 82%;
1233
+ display: flex;
1234
+ flex-direction: column;
1235
+ align-items: flex-end;
1236
+ gap: 4px;
1237
+ }
1238
+ .log-user {
1239
+ color: #ffffff;
1240
+ font-weight: 450;
1241
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
1242
+ padding: 10px 15px;
1243
+ border-radius: 16px 16px 4px 16px;
1244
+ white-space: pre-wrap;
1245
+ word-break: break-word;
1246
+ box-shadow: var(--shadow-sm);
1247
+ line-height: 1.55;
1248
+ }
1249
+
1250
+ /* Agent Message: Codex/Gemini open conversational layout */
1251
+ .agent-avatar {
1252
+ width: 32px;
1253
+ height: 32px;
1254
+ border-radius: 50%;
1255
+ background: linear-gradient(135deg, #0d766e 0%, #0284c7 100%);
1256
+ color: #ffffff;
1257
+ display: grid;
1258
+ place-items: center;
1259
+ font-weight: 700;
1260
+ font-size: 15px;
1261
+ flex-shrink: 0;
1262
+ box-shadow: 0 2px 4px rgba(13, 118, 110, 0.2);
1263
+ }
1264
+ .agent-content-wrap {
1265
+ flex: 1;
1266
+ min-width: 0;
1267
+ display: flex;
1268
+ flex-direction: column;
1269
+ gap: 5px;
1270
+ }
1271
+ .agent-meta {
1272
+ display: flex;
1273
+ align-items: center;
1274
+ gap: 8px;
1275
+ font-size: 12px;
1276
+ font-weight: 600;
1277
+ color: var(--text-secondary);
1278
+ }
1279
+ .agent-role-pill {
1280
+ font-size: 10px;
1281
+ font-weight: 550;
963
1282
  background: var(--brand-primary-light);
964
- border-color: var(--brand-primary);
965
- transform: translateY(-1px);
1283
+ color: var(--brand-primary);
1284
+ border: 1px solid var(--brand-primary-border);
1285
+ padding: 0 5px;
1286
+ border-radius: 4px;
966
1287
  }
967
- .console-drawer {
968
- position: fixed;
969
- bottom: 0;
970
- right: 28px;
971
- width: 480px;
972
- max-width: calc(100vw - 40px);
973
- height: 560px;
974
- max-height: calc(100vh - 80px);
1288
+ .log-agent {
1289
+ color: var(--text-main);
975
1290
  background: var(--bg-surface);
1291
+ padding: 14px 18px;
1292
+ border-radius: 4px 16px 16px 16px;
1293
+ border: 1px solid var(--border-subtle);
1294
+ word-break: break-word;
1295
+ box-shadow: var(--shadow-card);
1296
+ line-height: 1.65;
1297
+ }
1298
+ .log-agent > :first-child { margin-top: 0; }
1299
+ .log-agent > :last-child { margin-bottom: 0; }
1300
+ .log-agent p { margin: 8px 0; }
1301
+ .log-agent h1, .log-agent h2, .log-agent h3 {
1302
+ margin: 14px 0 6px;
976
1303
  color: var(--text-main);
1304
+ font-weight: 650;
1305
+ }
1306
+ .log-agent h1 { font-size: 16px; }
1307
+ .log-agent h2 { font-size: 14.5px; }
1308
+ .log-agent h3 { font-size: 13.5px; }
1309
+ .log-agent ul, .log-agent ol {
1310
+ margin: 6px 0 6px 20px;
1311
+ padding: 0;
1312
+ }
1313
+ .log-agent li { margin: 3px 0; }
1314
+ .log-agent blockquote {
1315
+ border-left: 3px solid var(--brand-primary);
1316
+ background: var(--brand-primary-light);
1317
+ padding: 6px 12px;
1318
+ margin: 8px 0;
1319
+ border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
1320
+ color: var(--text-secondary);
1321
+ }
1322
+ .log-agent table {
1323
+ width: 100%;
1324
+ border-collapse: collapse;
1325
+ margin: 10px 0;
1326
+ font-size: 12.5px;
1327
+ }
1328
+ .log-agent th, .log-agent td {
1329
+ padding: 6px 10px;
977
1330
  border: 1px solid var(--border-subtle);
978
- border-bottom: 0;
979
- border-radius: var(--radius-lg) var(--radius-lg) 0 0;
980
- box-shadow: var(--shadow-md);
981
- display: flex;
982
- flex-direction: column;
983
- z-index: 1000;
984
- transform: translateY(105%);
985
- transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
986
- font-family: var(--font-sans);
1331
+ text-align: left;
987
1332
  }
988
- .console-drawer.open {
989
- transform: translateY(0);
1333
+ .log-agent th {
1334
+ background: var(--bg-surface-subtle);
1335
+ font-weight: 600;
990
1336
  }
991
- .console-drawer.embedded {
992
- border: none;
993
- border-radius: 0;
994
- box-shadow: none;
995
- transform: none;
1337
+
1338
+ /* Code Blocks with Copy Header */
1339
+ .code-container {
1340
+ position: relative;
1341
+ margin: 10px 0;
1342
+ border-radius: var(--radius-md);
1343
+ overflow: hidden;
1344
+ background: #0f172a;
1345
+ border: 1px solid #1e293b;
996
1346
  }
997
- .console-header {
1347
+ .code-header {
998
1348
  display: flex;
999
1349
  align-items: center;
1000
1350
  justify-content: space-between;
1001
- padding: 12px 16px;
1002
- border-bottom: 1px solid var(--border-subtle);
1003
- background: var(--bg-surface);
1004
- border-radius: var(--radius-lg) var(--radius-lg) 0 0;
1351
+ padding: 5px 12px;
1352
+ background: #1e293b;
1353
+ color: #94a3b8;
1354
+ font-size: 11px;
1355
+ font-family: var(--font-mono);
1356
+ user-select: none;
1357
+ }
1358
+ .code-copy-btn {
1359
+ display: inline-flex;
1360
+ align-items: center;
1361
+ gap: 4px;
1362
+ font-size: 11px;
1363
+ color: #cbd5e1;
1364
+ background: transparent;
1365
+ border: none;
1366
+ padding: 2px 6px;
1367
+ border-radius: 4px;
1368
+ cursor: pointer;
1369
+ transition: background 0.12s ease, color 0.12s ease;
1370
+ }
1371
+ .code-copy-btn:hover {
1372
+ background: #334155;
1373
+ color: #ffffff;
1374
+ }
1375
+ .log-agent pre {
1376
+ margin: 0;
1377
+ padding: 12px 14px;
1378
+ overflow-x: auto;
1379
+ font-family: var(--font-mono);
1380
+ font-size: 12px;
1381
+ line-height: 1.5;
1382
+ color: #f1f5f9;
1383
+ background: transparent;
1005
1384
  }
1006
- .console-drawer.embedded .console-header {
1007
- border-radius: 0;
1385
+ .log-agent code {
1386
+ font-family: var(--font-mono);
1387
+ font-size: 12px;
1008
1388
  }
1009
- .console-title {
1010
- font-size: 13px;
1011
- font-weight: 650;
1012
- color: var(--text-main);
1013
- display: flex;
1389
+ .log-agent p code, .log-agent li code {
1390
+ background: var(--bg-surface-subtle);
1391
+ color: #0f766e;
1392
+ border: 1px solid var(--border-subtle);
1393
+ padding: 1px 5px;
1394
+ border-radius: 4px;
1395
+ }
1396
+
1397
+ /* Shimmering Pulsing Dots for Streaming */
1398
+ .agent-typing-indicator {
1399
+ display: inline-flex;
1014
1400
  align-items: center;
1015
- gap: 8px;
1401
+ gap: 5px;
1402
+ padding: 6px 12px;
1403
+ background: var(--bg-surface);
1404
+ border: 1px solid var(--border-subtle);
1405
+ border-radius: 12px;
1406
+ margin-top: 4px;
1407
+ width: fit-content;
1016
1408
  }
1017
- .console-status-tag {
1018
- font-size: 10px;
1019
- font-weight: 600;
1020
- padding: 1px 6px;
1021
- border-radius: var(--radius-xs);
1022
- background: var(--ok-bg);
1023
- color: var(--ok-text);
1024
- border: 1px solid var(--ok-border);
1409
+ .typing-dot {
1410
+ width: 6px;
1411
+ height: 6px;
1412
+ border-radius: 50%;
1413
+ background: var(--brand-primary);
1414
+ animation: pulseDot 1.4s infinite ease-in-out both;
1025
1415
  }
1026
- .console-actions {
1027
- display: flex;
1028
- align-items: center;
1029
- gap: 8px;
1416
+ .typing-dot:nth-child(1) { animation-delay: -0.32s; }
1417
+ .typing-dot:nth-child(2) { animation-delay: -0.16s; }
1418
+ .typing-dot:nth-child(3) { animation-delay: 0s; }
1419
+ @keyframes pulseDot {
1420
+ 0%, 80%, 100% { transform: scale(0.6); opacity: 0.35; }
1421
+ 40% { transform: scale(1.1); opacity: 1; }
1030
1422
  }
1031
- .console-btn-icon {
1032
- background: transparent;
1033
- border: none;
1423
+
1424
+ /* System & Info Messages */
1425
+ .log-sys {
1034
1426
  color: var(--text-muted);
1035
- cursor: pointer;
1036
- padding: 4px;
1427
+ font-size: 12px;
1428
+ background: var(--bg-surface-subtle);
1429
+ border: 1px dashed var(--border-default);
1430
+ padding: 8px 12px;
1037
1431
  border-radius: var(--radius-sm);
1038
- display: flex;
1432
+ text-align: center;
1433
+ width: 100%;
1039
1434
  }
1040
- .console-btn-icon:hover {
1041
- color: var(--text-main);
1042
- background: var(--bg-surface-subtle);
1435
+ .wake-checklist-card {
1436
+ background: var(--bg-surface);
1437
+ border: 1px solid var(--brand-primary-border);
1438
+ border-left: 3px solid var(--brand-primary);
1439
+ border-radius: var(--radius-sm);
1440
+ padding: 10px 14px;
1441
+ text-align: left;
1043
1442
  }
1443
+
1444
+ /* Quick Action Chips */
1044
1445
  .console-quick-chips {
1045
1446
  display: flex;
1046
1447
  align-items: center;
1047
1448
  gap: 6px;
1048
- padding: 8px 14px;
1049
- background: var(--bg-surface-subtle);
1050
- border-bottom: 1px solid var(--border-subtle);
1449
+ padding: 8px 16px;
1450
+ background: var(--bg-surface);
1451
+ border-top: 1px solid var(--border-subtle);
1051
1452
  overflow-x: auto;
1453
+ max-width: 860px;
1454
+ margin: 0 auto;
1455
+ width: 100%;
1052
1456
  }
1053
1457
  .chip {
1054
1458
  font-family: var(--font-mono);
1055
1459
  font-size: 11px;
1056
- background: var(--bg-surface);
1460
+ background: var(--bg-surface-subtle);
1057
1461
  color: var(--text-secondary);
1058
- border: 1px solid var(--border-default);
1059
- padding: 3px 8px;
1060
- border-radius: var(--radius-sm);
1462
+ border: 1px solid var(--border-subtle);
1463
+ padding: 3px 9px;
1464
+ border-radius: 999px;
1061
1465
  cursor: pointer;
1062
1466
  white-space: nowrap;
1063
- transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
1467
+ transition: all 0.15s ease;
1468
+ display: inline-flex;
1469
+ align-items: center;
1470
+ gap: 4px;
1064
1471
  }
1065
1472
  .chip:hover {
1066
1473
  background: var(--brand-primary-light);
1067
1474
  border-color: var(--brand-primary-border);
1068
1475
  color: var(--brand-primary);
1069
1476
  }
1070
- .console-log {
1071
- flex: 1;
1072
- overflow-y: auto;
1073
- padding: 16px;
1074
- font-family: var(--font-sans);
1075
- font-size: 13px;
1076
- line-height: 1.55;
1077
- display: flex;
1078
- flex-direction: column;
1079
- gap: 12px;
1080
- background: var(--bg-app);
1477
+
1478
+ /* Modern Textarea Input Box (Gemini / Codex Style) */
1479
+ .console-input-wrap {
1480
+ background: var(--bg-surface);
1481
+ border-top: 1px solid var(--border-subtle);
1482
+ padding: 12px 16px 14px;
1081
1483
  }
1082
- .log-entry {
1484
+ .console-input-container {
1485
+ max-width: 860px;
1486
+ margin: 0 auto;
1083
1487
  display: flex;
1084
1488
  flex-direction: column;
1085
- gap: 4px;
1086
- max-width: 92%;
1087
- }
1088
- .log-entry.user {
1089
- align-self: flex-end;
1090
- align-items: flex-end;
1091
- }
1092
- .log-entry.agent,
1093
- .log-entry.sys {
1094
- align-self: flex-start;
1095
- align-items: flex-start;
1096
- }
1097
- .log-user {
1098
- color: #fff;
1099
- font-weight: 500;
1100
- background: var(--brand-primary);
1101
- padding: 10px 12px;
1102
- border-radius: 12px 12px 4px 12px;
1103
- white-space: pre-wrap;
1104
- word-break: break-word;
1105
- box-shadow: var(--shadow-xs);
1106
- }
1107
- .log-agent {
1108
- color: var(--text-main);
1109
- background: var(--bg-surface);
1110
- padding: 10px 12px;
1111
- border-radius: 12px 12px 12px 4px;
1112
- border: 1px solid var(--border-subtle);
1113
- white-space: pre-wrap;
1114
- word-break: break-word;
1115
- box-shadow: var(--shadow-xs);
1116
- }
1117
- .log-sys {
1118
- color: var(--text-muted);
1119
- font-style: normal;
1120
- font-size: 12px;
1121
- background: transparent;
1122
- padding: 2px 0;
1489
+ gap: 6px;
1123
1490
  }
1124
1491
  .console-input-box {
1125
- padding: 12px 14px;
1126
- background: var(--bg-surface);
1127
- border-top: 1px solid var(--border-subtle);
1492
+ background: var(--bg-surface-subtle);
1493
+ border: 1.5px solid var(--border-default);
1494
+ border-radius: var(--radius-lg);
1495
+ padding: 8px 12px;
1128
1496
  display: flex;
1497
+ align-items: flex-end;
1129
1498
  gap: 8px;
1499
+ transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
1500
+ }
1501
+ .console-input-box:focus-within {
1502
+ background: var(--bg-surface);
1503
+ border-color: var(--brand-primary);
1504
+ box-shadow: 0 0 0 3px var(--brand-primary-border);
1130
1505
  }
1131
- .console-input {
1506
+ .console-textarea {
1132
1507
  flex: 1;
1133
- background: var(--bg-surface-subtle);
1134
- border: 1px solid var(--border-default);
1135
- color: var(--text-main);
1136
- padding: 10px 12px;
1137
- font-size: 13px;
1138
- border-radius: var(--radius-md);
1508
+ background: transparent;
1509
+ border: none;
1139
1510
  outline: none;
1511
+ resize: none;
1140
1512
  font-family: var(--font-sans);
1513
+ font-size: 13.5px;
1514
+ line-height: 1.5;
1515
+ color: var(--text-main);
1516
+ min-height: 24px;
1517
+ max-height: 160px;
1518
+ padding: 4px 0;
1141
1519
  }
1142
- .console-input:focus {
1143
- border-color: var(--brand-primary);
1144
- background: var(--bg-surface);
1145
- box-shadow: 0 0 0 3px var(--brand-primary-border);
1520
+ .console-textarea::placeholder {
1521
+ color: var(--text-muted);
1146
1522
  }
1147
1523
  .console-send-btn {
1148
1524
  background: var(--brand-primary);
1149
- color: #fff;
1525
+ color: #ffffff;
1150
1526
  border: none;
1151
- padding: 8px 14px;
1527
+ width: 32px;
1528
+ height: 32px;
1152
1529
  border-radius: var(--radius-md);
1153
- font-size: 13px;
1154
- font-weight: 600;
1155
1530
  cursor: pointer;
1156
- display: flex;
1157
- align-items: center;
1158
- gap: 4px;
1531
+ display: grid;
1532
+ place-items: center;
1533
+ flex-shrink: 0;
1534
+ transition: background 0.15s ease, transform 0.1s ease;
1535
+ margin-bottom: 2px;
1159
1536
  }
1160
- .console-send-btn:hover {
1537
+ .console-send-btn:hover:not(:disabled) {
1161
1538
  background: var(--brand-primary-hover);
1539
+ transform: translateY(-1px);
1162
1540
  }
1163
- .log-agent.streaming::after {
1164
- content: "▍";
1165
- animation: blink 1s steps(1) infinite;
1166
- color: var(--brand-primary);
1167
- margin-left: 2px;
1541
+ .console-send-btn:disabled {
1542
+ opacity: 0.45;
1543
+ cursor: not-allowed;
1544
+ }
1545
+ .console-input-footer {
1546
+ display: flex;
1547
+ align-items: center;
1548
+ justify-content: space-between;
1549
+ padding: 0 4px;
1550
+ font-size: 11px;
1551
+ color: var(--text-muted);
1168
1552
  }
1169
- @keyframes blink { 50% { opacity: 0; } }
1170
1553
  .wake-option-list {
1171
1554
  display: grid;
1172
1555
  gap: 8px;
@@ -1280,7 +1663,6 @@
1280
1663
  .viewer { min-height: 480px; }
1281
1664
  .provider-grid, .cost-metrics { grid-template-columns: 1fr; }
1282
1665
  .topbar { padding: 0 14px; }
1283
- .console-drawer { width: 100vw; right: 0; }
1284
1666
  }
1285
1667
  </style>
1286
1668
  </head>
@@ -1305,9 +1687,9 @@
1305
1687
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
1306
1688
  /plan 方案
1307
1689
  </button>
1308
- <button id="cmd-verify-btn" class="topbar-btn" title="关键字段人机核查清单 (/verify)">
1690
+ <button id="cmd-verify-btn" class="topbar-btn" title="AI 核查清单;人审在此核实 (/crosscheck)">
1309
1691
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
1310
- /verify 核查
1692
+ /crosscheck
1311
1693
  </button>
1312
1694
  </div>
1313
1695
  <div class="local-badge">
@@ -1320,32 +1702,37 @@
1320
1702
  <!-- 主体工作区 Layout -->
1321
1703
  <div class="layout">
1322
1704
  <!-- 侧边导航栏 Sidebar -->
1323
- <nav class="nav" aria-label="Panel 导航">
1705
+ <nav class="nav" id="workbench-nav" aria-label="Panel 导航">
1324
1706
  <div class="nav-group">
1325
- <div class="nav-label">工作台导航</div>
1326
- <button class="active" data-view="project">
1707
+ <div class="nav-header">
1708
+ <div class="nav-label">工作台导航</div>
1709
+ <button id="nav-toggle-btn" class="nav-toggle-btn" title="折叠/展开导航栏" aria-label="折叠导航栏">
1710
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
1711
+ </button>
1712
+ </div>
1713
+ <button class="active" data-view="project" title="项目文件">
1327
1714
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/></svg>
1328
- 项目文件
1715
+ <span class="nav-btn-text">项目文件</span>
1329
1716
  </button>
1330
- <button data-view="ecosystem">
1717
+ <button data-view="ecosystem" title="能力生态">
1331
1718
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>
1332
- 能力生态
1719
+ <span class="nav-btn-text">能力生态</span>
1333
1720
  </button>
1334
- <button data-view="provider">
1721
+ <button data-view="provider" title="模型与成本">
1335
1722
  <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" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
1336
- 模型与成本
1723
+ <span class="nav-btn-text">模型与成本</span>
1337
1724
  </button>
1338
- <button data-view="help">
1725
+ <button data-view="help" title="使用速览">
1339
1726
  <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="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
1340
- 使用速览
1727
+ <span class="nav-btn-text">使用速览</span>
1341
1728
  </button>
1342
- <button data-view="checklist">
1729
+ <button data-view="checklist" title="核对清单">
1343
1730
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
1344
- 核对清单
1731
+ <span class="nav-btn-text">核对清单</span>
1345
1732
  </button>
1346
- <button data-view="chat">
1733
+ <button data-view="chat" title="对话">
1347
1734
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
1348
- 对话
1735
+ <span class="nav-btn-text">对话</span>
1349
1736
  </button>
1350
1737
  </div>
1351
1738
  <div class="nav-footer">
@@ -1548,11 +1935,12 @@
1548
1935
  </section>
1549
1936
 
1550
1937
  <section id="view-help" class="view">
1551
- <div class="page-head">
1938
+ <div class="help-hero">
1552
1939
  <div>
1553
1940
  <h1 id="help-title">使用速览</h1>
1554
- <p id="help-thinking">加载中…</p>
1941
+ <p id="help-subtitle">三种模式 · AI 交叉核验 · 内置学术工作流</p>
1555
1942
  </div>
1943
+ <div id="help-thinking" class="help-kbd">加载中…</div>
1556
1944
  </div>
1557
1945
  <div class="help-grid" id="help-modes"></div>
1558
1946
  <div class="help-block">
@@ -1573,11 +1961,11 @@
1573
1961
  <div class="page-head">
1574
1962
  <div>
1575
1963
  <h1>交叉核验清单</h1>
1576
- <p>分析前/后由模型提出项,人在此勾选。可跳过,但会标注「未经核对」。本地写入 <code>.psyclaw/verify-checklist.json</code>。</p>
1964
+ <p>AI <code>/crosscheck</code> 核查字段;<strong>analysis / academic 收尾必须在此点「核实」</strong>(或终端 <code>/crosscheck &lt;id&gt; human</code>)。AI 已核与跳过都不算通过。</p>
1577
1965
  </div>
1578
1966
  <div style="display:flex;gap:8px">
1579
1967
  <button id="refresh-checklist" class="button">刷新</button>
1580
- <button id="skip-checklist" class="button" title="其余未核项标为跳过/未经核对">跳过未核项</button>
1968
+ <button id="skip-checklist" class="button" title="标为跳过;不解除 analysis/academic 人审门禁">跳过未核项</button>
1581
1969
  </div>
1582
1970
  </div>
1583
1971
  <div class="panel section">
@@ -1593,62 +1981,79 @@
1593
1981
  </section>
1594
1982
 
1595
1983
  <section id="view-chat" class="view">
1596
- <div class="page-head">
1984
+ <div class="page-head" style="margin-bottom: 12px;">
1597
1985
  <div>
1598
1986
  <h1>交互对话</h1>
1599
- <p>提问会转入当前 PsyClaw 会话;助手回复经 SSE 同步到此处。模型可调用「唤醒选项」弹窗。</p>
1987
+ <p>提问将接入当前 PsyClaw 会话;助手回复经 SSE 流式同步,支持学术指令与「唤醒选项」决策。</p>
1600
1988
  </div>
1601
1989
  </div>
1602
- <div id="chat-console-host" class="panel section" style="min-height:62vh;padding:0;overflow:hidden;display:flex;flex-direction:column"></div>
1603
- </section>
1604
- </main>
1605
- </div>
1606
- </div>
1607
-
1608
- <!-- 悬浮对话入口 -->
1609
- <button id="console-fab" class="console-fab" title="打开/折叠对话">
1610
- <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
1611
- <span>对话</span>
1612
- </button>
1613
-
1614
- <!-- 抽屉式对话 -->
1615
- <div id="console-drawer" class="console-drawer">
1616
- <div class="console-header">
1617
- <div class="console-title">
1618
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--brand-primary)"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
1619
- <span>对话</span>
1620
- <span id="console-status-badge" class="console-status-tag">已连接</span>
1621
- </div>
1622
- <div class="console-actions">
1623
- <button id="console-clear-btn" class="console-btn-icon" title="清空对话">
1624
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
1625
- </button>
1626
- <button id="console-close-btn" class="console-btn-icon" title="最小化">
1627
- <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
1628
- </button>
1629
- </div>
1630
- </div>
1990
+ <div id="chat-console-host" class="panel section" style="min-height:74vh;padding:0;overflow:hidden;display:flex;flex-direction:column;border-radius:var(--radius-md);">
1991
+ <div id="console-drawer" class="console-drawer">
1992
+ <div class="console-header">
1993
+ <div class="console-title">
1994
+ <span class="agent-avatar" style="width:24px;height:24px;font-size:12px">ψ</span>
1995
+ <span>PsyClaw Research Assistant</span>
1996
+ <span id="console-status-badge" class="console-status-tag">
1997
+ <span class="pulse-dot" style="width:5px;height:5px;"></span>
1998
+ 已连接
1999
+ </span>
2000
+ </div>
2001
+ <div class="console-actions">
2002
+ <button id="console-clear-btn" class="console-btn-icon" title="清空对话记录">
2003
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
2004
+ </button>
2005
+ </div>
2006
+ </div>
1631
2007
 
1632
- <div class="console-quick-chips">
1633
- <span class="chip" data-cmd="/plan status">/plan status</span>
1634
- <span class="chip" data-cmd="/verify list">/verify list</span>
1635
- <span class="chip" data-cmd="/grill">/grill 压力追问</span>
1636
- <span class="chip" data-cmd="/review">/review 同行评审</span>
1637
- <span class="chip" data-cmd="/agents">/agents 角色</span>
1638
- </div>
2008
+ <div id="console-log" class="console-log">
2009
+ <div class="log-entry sys">
2010
+ <div class="log-sys">
2011
+ 💡 <strong>对话已就绪</strong>:与 PsyClaw 智能体安全连接。支持 <code>/init</code>、<code>/plan</code>、<code>/grill</code>、<code>/crosscheck</code>(AI 核验)等指令。
2012
+ </div>
2013
+ </div>
2014
+ </div>
1639
2015
 
1640
- <div id="console-log" class="console-log">
1641
- <div class="log-entry sys">
1642
- <div class="log-sys">对话已就绪(SSE)。提问转入当前会话,助手回复会流式同步到此处。支持 /init、/plan、/grill 等指令;模型可弹出「唤醒选项」。</div>
1643
- </div>
1644
- </div>
2016
+ <div class="console-quick-chips">
2017
+ <span class="chip" data-cmd="/plan status">
2018
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
2019
+ /plan status
2020
+ </span>
2021
+ <span class="chip" data-cmd="/crosscheck list">
2022
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11l3 3L22 4"/></svg>
2023
+ /crosscheck list
2024
+ </span>
2025
+ <span class="chip" data-cmd="/grill">
2026
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/></svg>
2027
+ /grill 压力追问
2028
+ </span>
2029
+ <span class="chip" data-cmd="/review">
2030
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/></svg>
2031
+ /review 评审
2032
+ </span>
2033
+ <span class="chip" data-cmd="/agents">
2034
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg>
2035
+ /agents 角色
2036
+ </span>
2037
+ </div>
1645
2038
 
1646
- <div class="console-input-box">
1647
- <input type="text" id="console-input" class="console-input" placeholder="输入研究问题或指令,Enter 发送…" autocomplete="off" />
1648
- <button id="console-send-btn" class="console-send-btn">
1649
- <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
1650
- 发送
1651
- </button>
2039
+ <div class="console-input-wrap">
2040
+ <div class="console-input-container">
2041
+ <div class="console-input-box">
2042
+ <textarea id="console-input" class="console-textarea" placeholder="输入研究问题或指令(Enter 发送,Shift+Enter 换行)…" rows="1"></textarea>
2043
+ <button id="console-send-btn" class="console-send-btn" title="发送消息 (Enter)">
2044
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
2045
+ </button>
2046
+ </div>
2047
+ <div class="console-input-footer">
2048
+ <span>支持 Markdown 与学术指令</span>
2049
+ <span>PsyClaw 本地隔离环境</span>
2050
+ </div>
2051
+ </div>
2052
+ </div>
2053
+ </div>
2054
+ </div>
2055
+ </section>
2056
+ </main>
1652
2057
  </div>
1653
2058
  </div>
1654
2059
 
@@ -1750,24 +2155,38 @@ function setView(view) {
1750
2155
  if (view === "provider") loadProvider();
1751
2156
  if (view === "checklist") loadChecklist();
1752
2157
  if (view === "help") loadHelp();
1753
- if (view === "chat") mountConsoleInChat();
1754
- else restoreConsoleDrawer();
2158
+ if (view === "chat") queueMicrotask(() => $("console-input")?.focus());
1755
2159
  }
1756
2160
 
1757
2161
  async function loadHelp() {
1758
2162
  try {
1759
2163
  const doc = await json("/api/help");
1760
2164
  $("help-title").textContent = doc.title || "使用速览";
2165
+ $("help-subtitle").textContent = doc.subtitle || "";
1761
2166
  $("help-thinking").textContent = doc.thinking || "";
1762
2167
  $("help-modes").innerHTML = (doc.modes || []).map(mode => `
1763
2168
  <article class="help-mode-card">
1764
- <div class="mode-id">${esc(mode.label || mode.id)}</div>
2169
+ <div class="mode-id">${esc(mode.hint || mode.id)}</div>
2170
+ <div class="mode-label">${esc(mode.label || mode.id)}</div>
1765
2171
  <p>${esc(mode.blurb)}</p>
1766
2172
  </article>
1767
2173
  `).join("");
1768
- $("help-steps").innerHTML = (doc.steps || []).map(step => `<li>${esc(step)}</li>`).join("");
1769
- $("help-commands").innerHTML = (doc.commands || []).map(row => `
1770
- <div class="help-cmd"><code>${esc(row.cmd)}</code><span>${esc(row.blurb)}</span></div>
2174
+ $("help-steps").innerHTML = (doc.steps || []).map((step, index) =>
2175
+ `<li data-step="${index + 1}">${esc(step)}</li>`
2176
+ ).join("");
2177
+ const groups = new Map();
2178
+ for (const row of (doc.commands || [])) {
2179
+ const key = row.group || "其他";
2180
+ if (!groups.has(key)) groups.set(key, []);
2181
+ groups.get(key).push(row);
2182
+ }
2183
+ $("help-commands").innerHTML = [...groups.entries()].map(([group, rows]) => `
2184
+ <div class="help-cmd-group">
2185
+ <h3>${esc(group)}</h3>
2186
+ <div class="help-cmd-list">
2187
+ ${rows.map(row => `<div class="help-cmd"><code>${esc(row.cmd)}</code><span>${esc(row.blurb)}</span></div>`).join("")}
2188
+ </div>
2189
+ </div>
1771
2190
  `).join("");
1772
2191
  $("help-notes").innerHTML = (doc.notes || []).map(note => `<li>${esc(note)}</li>`).join("");
1773
2192
  } catch (error) {
@@ -1775,37 +2194,6 @@ async function loadHelp() {
1775
2194
  }
1776
2195
  }
1777
2196
 
1778
- function mountConsoleInChat() {
1779
- const host = $("chat-console-host");
1780
- const drawer = $("console-drawer");
1781
- if (!host || !drawer) return;
1782
- host.appendChild(drawer);
1783
- drawer.classList.add("open", "embedded");
1784
- drawer.style.position = "relative";
1785
- drawer.style.inset = "auto";
1786
- drawer.style.width = "100%";
1787
- drawer.style.height = "100%";
1788
- drawer.style.minHeight = "58vh";
1789
- drawer.style.boxShadow = "none";
1790
- drawer.style.borderRadius = "0";
1791
- drawer.style.transform = "none";
1792
- }
1793
-
1794
- function restoreConsoleDrawer() {
1795
- const drawer = $("console-drawer");
1796
- if (!drawer || drawer.parentElement === document.body) return;
1797
- document.body.appendChild(drawer);
1798
- drawer.classList.remove("embedded");
1799
- drawer.style.position = "";
1800
- drawer.style.inset = "";
1801
- drawer.style.width = "";
1802
- drawer.style.height = "";
1803
- drawer.style.minHeight = "";
1804
- drawer.style.boxShadow = "";
1805
- drawer.style.borderRadius = "";
1806
- drawer.style.transform = "";
1807
- }
1808
-
1809
2197
  async function loadChecklist() {
1810
2198
  const body = $("checklist-body");
1811
2199
  if (!body) return;
@@ -1845,7 +2233,7 @@ async function loadChecklist() {
1845
2233
 
1846
2234
  $("refresh-checklist")?.addEventListener("click", loadChecklist);
1847
2235
  $("skip-checklist")?.addEventListener("click", async () => {
1848
- if (!confirm("将把所有未核项标为跳过,并写入「未经核对」。继续?")) return;
2236
+ if (!confirm("将把未核/AI已核项标为跳过。注意:这不会解除 analysis/academic 收尾人审门禁。继续?")) return;
1849
2237
  await json("/api/crosscheck", {
1850
2238
  method: "POST",
1851
2239
  headers: { "content-type": "application/json" },
@@ -1854,7 +2242,7 @@ $("skip-checklist")?.addEventListener("click", async () => {
1854
2242
  await loadChecklist();
1855
2243
  });
1856
2244
 
1857
- document.querySelectorAll(".nav button").forEach(button => {
2245
+ document.querySelectorAll(".nav button[data-view]").forEach(button => {
1858
2246
  button.addEventListener("click", () => setView(button.dataset.view));
1859
2247
  });
1860
2248
 
@@ -1873,16 +2261,20 @@ function inlineMarkdown(text) {
1873
2261
 
1874
2262
  function renderMarkdown(markdown) {
1875
2263
  const lines = String(markdown).replace(/\r\n/g, "\n").split("\n"), out = [];
1876
- let i = 0, inCode = false, code = [];
2264
+ let i = 0, inCode = false, codeLang = "", code = [];
1877
2265
  while (i < lines.length) {
1878
2266
  const line = lines[i], trim = line.trim();
1879
2267
  if (trim.startsWith("```")) {
1880
2268
  if (inCode) {
1881
- out.push(`<pre><code>${esc(code.join("\n"))}</code></pre>`);
2269
+ const rawCode = code.join("\n");
2270
+ const langDisplay = codeLang || "code";
2271
+ out.push(`<div class="code-container"><div class="code-header"><span>${esc(langDisplay)}</span><button type="button" class="code-copy-btn" data-code="${esc(rawCode)}"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg><span>复制</span></button></div><pre><code>${esc(rawCode)}</code></pre></div>`);
1882
2272
  code = [];
2273
+ codeLang = "";
1883
2274
  inCode = false;
1884
2275
  } else {
1885
2276
  inCode = true;
2277
+ codeLang = trim.slice(3).trim();
1886
2278
  }
1887
2279
  i++;
1888
2280
  continue;
@@ -1942,7 +2334,11 @@ function renderMarkdown(markdown) {
1942
2334
  }
1943
2335
  out.push(`<p>${inlineMarkdown(para.join(" "))}</p>`);
1944
2336
  }
1945
- if (inCode) out.push(`<pre><code>${esc(code.join("\n"))}</code></pre>`);
2337
+ if (inCode) {
2338
+ const rawCode = code.join("\n");
2339
+ const langDisplay = codeLang || "code";
2340
+ out.push(`<div class="code-container"><div class="code-header"><span>${esc(langDisplay)}</span><button type="button" class="code-copy-btn" data-code="${esc(rawCode)}"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg><span>复制</span></button></div><pre><code>${esc(rawCode)}</code></pre></div>`);
2341
+ }
1946
2342
  return out.join("");
1947
2343
  }
1948
2344
 
@@ -2343,8 +2739,6 @@ document.querySelectorAll(".step-item").forEach(item => {
2343
2739
  });
2344
2740
 
2345
2741
  /* --- 交互对话 + SSE + 唤醒选项 --- */
2346
- const consoleDrawer = $("console-drawer");
2347
- const consoleFab = $("console-fab");
2348
2742
  const consoleLog = $("console-log");
2349
2743
  const consoleInput = $("console-input");
2350
2744
  const streamBubbles = new Map();
@@ -2352,12 +2746,8 @@ let lastLocalUserText = "";
2352
2746
  let wakeState = null;
2353
2747
 
2354
2748
  function openConsole() {
2355
- consoleDrawer.classList.add("open");
2356
- consoleInput.focus();
2357
- }
2358
-
2359
- function closeConsole() {
2360
- consoleDrawer.classList.remove("open");
2749
+ setView("chat");
2750
+ queueMicrotask(() => consoleInput?.focus());
2361
2751
  }
2362
2752
 
2363
2753
  function openConsoleWithMessage(text) {
@@ -2369,9 +2759,22 @@ function appendLog(role, text) {
2369
2759
  const entry = document.createElement("div");
2370
2760
  entry.className = `log-entry ${role}`;
2371
2761
  if (role === "user") {
2372
- entry.innerHTML = `<div class="log-user">${esc(text)}</div>`;
2762
+ entry.innerHTML = `
2763
+ <div class="log-user-wrap">
2764
+ <div class="log-user">${esc(text)}</div>
2765
+ </div>
2766
+ `;
2373
2767
  } else if (role === "agent") {
2374
- entry.innerHTML = `<div class="log-agent">${renderMarkdown(text)}</div>`;
2768
+ entry.innerHTML = `
2769
+ <div class="agent-avatar">ψ</div>
2770
+ <div class="agent-content-wrap">
2771
+ <div class="agent-meta">
2772
+ <span>PsyClaw Assistant</span>
2773
+ <span class="agent-role-pill">Core</span>
2774
+ </div>
2775
+ <div class="log-agent">${renderMarkdown(text)}</div>
2776
+ </div>
2777
+ `;
2375
2778
  } else {
2376
2779
  entry.innerHTML = `<div class="log-sys">${esc(text)}</div>`;
2377
2780
  }
@@ -2386,7 +2789,21 @@ function ensureStreamBubble(messageId) {
2386
2789
  entry = document.createElement("div");
2387
2790
  entry.className = "log-entry agent";
2388
2791
  entry.dataset.messageId = messageId;
2389
- entry.innerHTML = `<div class="log-agent streaming"></div>`;
2792
+ entry.innerHTML = `
2793
+ <div class="agent-avatar">ψ</div>
2794
+ <div class="agent-content-wrap">
2795
+ <div class="agent-meta">
2796
+ <span>PsyClaw Assistant</span>
2797
+ <span class="agent-role-pill">Streaming</span>
2798
+ </div>
2799
+ <div class="log-agent" style="display:none"></div>
2800
+ <div class="agent-typing-indicator">
2801
+ <span class="typing-dot"></span>
2802
+ <span class="typing-dot"></span>
2803
+ <span class="typing-dot"></span>
2804
+ </div>
2805
+ </div>
2806
+ `;
2390
2807
  consoleLog.appendChild(entry);
2391
2808
  streamBubbles.set(messageId, entry);
2392
2809
  consoleLog.scrollTop = consoleLog.scrollHeight;
@@ -2396,9 +2813,24 @@ function ensureStreamBubble(messageId) {
2396
2813
  function updateStreamBubble(messageId, text, done) {
2397
2814
  const entry = ensureStreamBubble(messageId);
2398
2815
  const bubble = entry.querySelector(".log-agent");
2816
+ const indicator = entry.querySelector(".agent-typing-indicator");
2817
+ const pill = entry.querySelector(".agent-role-pill");
2399
2818
  if (!bubble) return;
2400
- bubble.classList.toggle("streaming", !done);
2401
- bubble.innerHTML = renderMarkdown(text || (done ? "" : "…"));
2819
+
2820
+ if (text) {
2821
+ bubble.style.display = "block";
2822
+ bubble.innerHTML = renderMarkdown(text);
2823
+ }
2824
+
2825
+ if (done) {
2826
+ if (indicator) indicator.remove();
2827
+ if (pill) pill.textContent = "Answered";
2828
+ bubble.style.display = "block";
2829
+ } else {
2830
+ if (text && indicator) {
2831
+ indicator.style.display = "none";
2832
+ }
2833
+ }
2402
2834
  consoleLog.scrollTop = consoleLog.scrollHeight;
2403
2835
  }
2404
2836
 
@@ -2450,7 +2882,10 @@ function connectAssistantStream() {
2450
2882
  es.addEventListener("wake_options", (ev) => {
2451
2883
  try {
2452
2884
  const data = JSON.parse(ev.data);
2453
- if (data.prompt) showWakeModal(data.prompt);
2885
+ if (data.prompt) {
2886
+ appendWakeChecklist(data.prompt);
2887
+ showWakeModal(data.prompt, { switchChecklist: false });
2888
+ }
2454
2889
  } catch { /* ignore */ }
2455
2890
  });
2456
2891
  es.addEventListener("wake_options_resolved", (ev) => {
@@ -2466,8 +2901,32 @@ function connectAssistantStream() {
2466
2901
  es.onerror = () => setConsoleStatus("重连中", false);
2467
2902
  }
2468
2903
 
2469
- function showWakeModal(prompt, { switchChecklist = true } = {}) {
2470
- const firstOpen = !wakeState || wakeState.id !== prompt.id;
2904
+ function appendWakeChecklist(prompt) {
2905
+ if (!prompt || prompt.mode !== "checklist") return;
2906
+ const existing = document.querySelector(`[data-wake-checklist-id="${CSS.escape(prompt.id)}"]`);
2907
+ if (existing) return;
2908
+ const entry = document.createElement("div");
2909
+ entry.className = "log-entry sys";
2910
+ entry.dataset.wakeChecklistId = prompt.id;
2911
+ const items = (prompt.options || []).map(option => `
2912
+ <li data-option-id="${esc(option.id)}"><span class="wake-checklist-mark">${option.checked ? "[x]" : "[ ]"}</span> <span>${esc(option.label)}</span>${option.description ? `<small>${esc(option.description)}</small>` : ""}</li>
2913
+ `).join("");
2914
+ entry.innerHTML = `<div class="log-sys wake-checklist-card"><strong>${esc(prompt.title || "交叉核验清单")}</strong>${prompt.prompt ? `<p>${esc(prompt.prompt)}</p>` : ""}<ul>${items}</ul><span class="wake-checklist-hint">请在当前对话中的弹窗完成勾选。</span></div>`;
2915
+ consoleLog.appendChild(entry);
2916
+ consoleLog.scrollTop = consoleLog.scrollHeight;
2917
+ }
2918
+
2919
+ function updateWakeChecklist(promptId, selectedIds) {
2920
+ const entry = document.querySelector(`[data-wake-checklist-id="${CSS.escape(promptId)}"]`);
2921
+ if (!entry) return;
2922
+ const selected = new Set(selectedIds || []);
2923
+ entry.querySelectorAll("li[data-option-id]").forEach(item => {
2924
+ const mark = item.querySelector(".wake-checklist-mark");
2925
+ if (mark) mark.textContent = selected.has(item.dataset.optionId) ? "[x]" : "[ ]";
2926
+ });
2927
+ }
2928
+
2929
+ function showWakeModal(prompt) {
2471
2930
  wakeState = {
2472
2931
  id: prompt.id,
2473
2932
  mode: prompt.mode,
@@ -2502,13 +2961,12 @@ function showWakeModal(prompt, { switchChecklist = true } = {}) {
2502
2961
  } else {
2503
2962
  wakeState.selected.add(option.id);
2504
2963
  }
2505
- showWakeModal(prompt, { switchChecklist: false });
2964
+ showWakeModal(prompt);
2506
2965
  });
2507
2966
  list.appendChild(btn);
2508
2967
  }
2509
2968
  $("wake-modal").classList.remove("hidden");
2510
2969
  openConsole();
2511
- if (firstOpen && switchChecklist && prompt.mode === "checklist") setView("checklist");
2512
2970
  }
2513
2971
 
2514
2972
  function hideWakeModal() {
@@ -2527,8 +2985,8 @@ async function submitWake(cancel = false) {
2527
2985
  body: JSON.stringify({ promptId, selectedIds, cancel: Boolean(cancel) }),
2528
2986
  });
2529
2987
  hideWakeModal();
2988
+ updateWakeChecklist(promptId, selectedIds);
2530
2989
  appendLog("sys", cancel ? "已取消唤醒选项" : `已提交选择:${selectedIds.join(", ") || "(空)"}`);
2531
- await loadChecklist().catch(() => {});
2532
2990
  } catch (err) {
2533
2991
  appendLog("sys", `[唤醒选项] ${err.message}`);
2534
2992
  }
@@ -2538,23 +2996,55 @@ $("wake-modal-close")?.addEventListener("click", () => submitWake(true));
2538
2996
  $("wake-cancel-btn")?.addEventListener("click", () => submitWake(true));
2539
2997
  $("wake-submit-btn")?.addEventListener("click", () => submitWake(false));
2540
2998
 
2541
- consoleFab.addEventListener("click", () => {
2542
- if (consoleDrawer.classList.contains("open")) closeConsole();
2543
- else openConsole();
2544
- });
2545
- $("console-close-btn").addEventListener("click", closeConsole);
2546
2999
  $("console-clear-btn").addEventListener("click", () => {
2547
3000
  consoleLog.innerHTML = `<div class="log-entry sys"><div class="log-sys">对话已清空。输入问题或 /help 即可继续。</div></div>`;
2548
3001
  streamBubbles.clear();
2549
3002
  });
2550
3003
 
3004
+ // 自适应高度调整函数
3005
+ function autoResizeConsoleInput() {
3006
+ if (!consoleInput) return;
3007
+ consoleInput.style.height = "auto";
3008
+ const nextHeight = Math.min(Math.max(consoleInput.scrollHeight, 24), 160);
3009
+ consoleInput.style.height = `${nextHeight}px`;
3010
+ }
3011
+
3012
+ consoleInput.addEventListener("input", autoResizeConsoleInput);
3013
+
3014
+ // 代码块全局点击复制事件代理
3015
+ document.addEventListener("click", async (e) => {
3016
+ const btn = e.target.closest(".code-copy-btn");
3017
+ if (!btn) return;
3018
+ const rawCode = btn.dataset.code;
3019
+ if (!rawCode) return;
3020
+ try {
3021
+ await navigator.clipboard.writeText(rawCode);
3022
+ const textSpan = btn.querySelector("span");
3023
+ if (textSpan) {
3024
+ const originalText = textSpan.textContent;
3025
+ textSpan.textContent = "已复制";
3026
+ btn.style.color = "var(--ok-text)";
3027
+ setTimeout(() => {
3028
+ textSpan.textContent = originalText;
3029
+ btn.style.color = "";
3030
+ }, 1800);
3031
+ }
3032
+ } catch {
3033
+ alert("无法写入剪贴板,请手动选中文本复制。");
3034
+ }
3035
+ });
3036
+
2551
3037
  async function sendConsoleMessage(msg) {
2552
3038
  const text = (msg || consoleInput.value).trim();
2553
3039
  if (!text) return;
2554
3040
  consoleInput.value = "";
3041
+ autoResizeConsoleInput();
2555
3042
  lastLocalUserText = text;
2556
3043
  appendLog("user", text);
2557
3044
 
3045
+ const sendBtn = $("console-send-btn");
3046
+ if (sendBtn) sendBtn.disabled = true;
3047
+
2558
3048
  try {
2559
3049
  const res = await json("/api/assistant", {
2560
3050
  method: "POST",
@@ -2565,6 +3055,8 @@ async function sendConsoleMessage(msg) {
2565
3055
  if (res.text) appendLog("sys", res.text);
2566
3056
  } catch (err) {
2567
3057
  appendLog("sys", `[提示] ${err.message}`);
3058
+ } finally {
3059
+ if (sendBtn) sendBtn.disabled = false;
2568
3060
  }
2569
3061
  }
2570
3062
 
@@ -2583,11 +3075,31 @@ document.querySelectorAll(".chip").forEach(chip => {
2583
3075
  const cmd = chip.dataset.cmd;
2584
3076
  if (cmd) {
2585
3077
  consoleInput.value = cmd;
3078
+ autoResizeConsoleInput();
2586
3079
  sendConsoleMessage(cmd);
2587
3080
  }
2588
3081
  });
2589
3082
  });
2590
3083
 
3084
+ /* --- 侧边栏折叠与展开交互 --- */
3085
+ const layoutEl = document.querySelector(".layout");
3086
+ const navToggleBtn = $("nav-toggle-btn");
3087
+
3088
+ function initNavCollapse() {
3089
+ const saved = localStorage.getItem("psyclaw_nav_collapsed");
3090
+ if (saved === "true") {
3091
+ layoutEl?.classList.add("nav-collapsed");
3092
+ }
3093
+ }
3094
+
3095
+ navToggleBtn?.addEventListener("click", () => {
3096
+ if (!layoutEl) return;
3097
+ const isCollapsed = layoutEl.classList.toggle("nav-collapsed");
3098
+ localStorage.setItem("psyclaw_nav_collapsed", isCollapsed ? "true" : "false");
3099
+ });
3100
+
3101
+ initNavCollapse();
3102
+
2591
3103
  /* --- 顶栏快捷命令 Quick Actions --- */
2592
3104
  const initModal = $("init-modal");
2593
3105
  const initParadigm = $("init-paradigm");