@hpyperry/dsh-ref-lib 0.7.0 → 0.8.0

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/lib/client.js CHANGED
@@ -313,31 +313,63 @@ window.__ModuleLoader__.load({
313
313
  * 本组件为纯展示:异步操作与错误归集由 RefLibDock 持有。
314
314
  * @module @hpyperry/dsh-ref-lib/src/client/RefLibPanel
315
315
  */
316
+ /** 用途说明最大长度(与 node half service.NOTE_MAX_LENGTH 一致)。 */
317
+ const NOTE_MAX_LENGTH = 120;
316
318
  /**
317
319
  * 渲染参考库管理面板。
318
320
  * @param props - 见 {@link RefLibPanelProps}。
319
321
  * @returns 对话框元素(关闭时 Modal 返回 null)。
320
322
  */
321
323
  function RefLibPanel(props) {
322
- const { open, onClose, libs, loading, busy, picking, removingId, error, t, onRemove, onAddPath, onBrowse } = props;
324
+ const { open, onClose, libs, loading, busy, picking, removingId, error, t, onRemove, onAddPath, onUpdateNote, onBrowse } = props;
323
325
  const [draft, setDraft] = (0, react.useState)("");
326
+ const [noteDraft, setNoteDraft] = (0, react.useState)("");
327
+ const [detailId, setDetailId] = (0, react.useState)(null);
328
+ const [editNote, setEditNote] = (0, react.useState)("");
324
329
  (0, react.useEffect)(() => {
325
- if (!open) setDraft("");
330
+ if (!open) {
331
+ setDraft("");
332
+ setNoteDraft("");
333
+ setDetailId(null);
334
+ }
326
335
  }, [open]);
327
336
  const handleSubmit = async () => {
328
337
  const trimmed = draft.trim();
329
338
  if (trimmed === "") return;
330
339
  try {
331
- await onAddPath(trimmed);
340
+ const note = noteDraft.trim();
341
+ await onAddPath(trimmed, note === "" ? void 0 : note);
332
342
  setDraft("");
343
+ setNoteDraft("");
333
344
  } catch {}
334
345
  };
346
+ /** 浏览选中目录 → 填入路径字段(不直接添加,用户可补用途后提交)。 */
347
+ const handleBrowse = async () => {
348
+ const path = await onBrowse();
349
+ if (path !== null) setDraft(path);
350
+ };
351
+ /** 展开/收起条目详情;展开时载入当前用途草稿。 */
352
+ const toggleDetail = (entry) => {
353
+ if (detailId === entry.id) {
354
+ setDetailId(null);
355
+ return;
356
+ }
357
+ setDetailId(entry.id);
358
+ setEditNote(entry.note ?? "");
359
+ };
360
+ /** 保存详情编辑的用途说明;成功后收起详情。 */
361
+ const handleSaveNote = async () => {
362
+ if (detailId === null) return;
363
+ await onUpdateNote(detailId, editNote);
364
+ setDetailId(null);
365
+ };
335
366
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
336
367
  open,
337
368
  onClose,
338
369
  title: t("panel.title"),
339
370
  closeLabel: t("panel.close"),
340
371
  description: t("panel.description", { count: String(libs.length) }),
372
+ className: "reflib-modal",
341
373
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
342
374
  className: "reflib-panel",
343
375
  children: [
@@ -380,45 +412,144 @@ window.__ModuleLoader__.load({
380
412
  children: libs.map((entry) => {
381
413
  const name = libBasename(entry.path);
382
414
  const removing = removingId === entry.id;
415
+ const detailOpen = detailId === entry.id;
383
416
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
384
- className: "reflib-row",
417
+ className: "reflib-listItem",
385
418
  "data-removing": removing || void 0,
386
- children: [
387
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
388
- size: 16,
389
- className: "reflib-rowIcon"
390
- }),
391
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
392
- className: "reflib-rowBody",
393
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
394
- className: "reflib-rowName",
395
- title: entry.path,
396
- children: name
397
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
398
- className: "reflib-rowPath",
399
- title: entry.path,
400
- children: entry.path
401
- })]
402
- }),
403
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
404
- label: t("list.remove"),
405
- side: "top",
406
- delayMs: 400,
407
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
408
- type: "button",
409
- className: "reflib-rowRemove",
410
- "aria-label": t("list.remove.aria", { name }),
411
- disabled: busy || removingId !== null,
412
- onClick: () => {
413
- onRemove(entry.id);
414
- },
415
- children: removing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
416
- size: 14,
417
- className: "reflib-spin"
418
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
419
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
420
+ className: "reflib-row",
421
+ children: [
422
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFolderOpen16, {
423
+ size: 16,
424
+ className: "reflib-rowIcon"
425
+ }),
426
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
427
+ className: "reflib-rowBody",
428
+ children: [
429
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
430
+ className: "reflib-rowName",
431
+ title: entry.path,
432
+ children: name
433
+ }),
434
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
435
+ className: "reflib-rowPath",
436
+ title: entry.path,
437
+ children: entry.path
438
+ }),
439
+ entry.note !== void 0 && entry.note !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
440
+ className: "reflib-rowNote",
441
+ title: entry.note,
442
+ children: entry.note.replace(/\s+/g, " ")
443
+ })
444
+ ]
445
+ }),
446
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
447
+ label: t("detail.open"),
448
+ side: "top",
449
+ delayMs: 400,
450
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
451
+ type: "button",
452
+ className: "reflib-rowAction",
453
+ "aria-label": t("detail.open.aria", { name }),
454
+ "aria-expanded": detailOpen || void 0,
455
+ disabled: busy || removingId !== null,
456
+ onClick: () => {
457
+ toggleDetail(entry);
458
+ },
459
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEllipsisOutline16, { size: 14 })
460
+ })
461
+ }),
462
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
463
+ label: t("list.remove"),
464
+ side: "top",
465
+ delayMs: 400,
466
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
467
+ type: "button",
468
+ className: "reflib-rowRemove",
469
+ "aria-label": t("list.remove.aria", { name }),
470
+ disabled: busy || removingId !== null,
471
+ onClick: () => {
472
+ onRemove(entry.id);
473
+ },
474
+ children: removing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {
475
+ size: 14,
476
+ className: "reflib-spin"
477
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
478
+ })
419
479
  })
420
- })
421
- ]
480
+ ]
481
+ }), detailOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
482
+ className: "reflib-detail",
483
+ children: [
484
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
485
+ className: "reflib-detailMeta",
486
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
487
+ className: "reflib-detailKey",
488
+ children: "ID"
489
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
490
+ className: "reflib-detailValue",
491
+ children: entry.id
492
+ })]
493
+ }),
494
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
495
+ className: "reflib-detailMeta",
496
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
497
+ className: "reflib-detailKey",
498
+ children: t("detail.path")
499
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
500
+ className: "reflib-detailValue",
501
+ title: entry.path,
502
+ children: entry.path
503
+ })]
504
+ }),
505
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
506
+ className: "reflib-detailKey",
507
+ children: t("add.note.label")
508
+ }),
509
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
510
+ className: "reflib-noteWrap",
511
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
512
+ className: "reflib-noteTextarea",
513
+ value: editNote,
514
+ placeholder: t("add.note.placeholder"),
515
+ "aria-label": t("add.note.label"),
516
+ disabled: busy,
517
+ rows: 3,
518
+ maxLength: NOTE_MAX_LENGTH,
519
+ onChange: (event) => {
520
+ setEditNote(event.currentTarget.value);
521
+ }
522
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
523
+ className: "reflib-noteCount",
524
+ children: [
525
+ editNote.length,
526
+ "/",
527
+ NOTE_MAX_LENGTH
528
+ ]
529
+ })]
530
+ }),
531
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
532
+ className: "reflib-detailActions",
533
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
534
+ variant: "ghost",
535
+ size: "sm",
536
+ disabled: busy,
537
+ onClick: () => {
538
+ setDetailId(null);
539
+ },
540
+ children: t("detail.cancel")
541
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
542
+ variant: "primary",
543
+ size: "sm",
544
+ disabled: busy,
545
+ onClick: () => {
546
+ handleSaveNote();
547
+ },
548
+ children: t("detail.save")
549
+ })]
550
+ })
551
+ ]
552
+ })]
422
553
  }, entry.id);
423
554
  })
424
555
  }),
@@ -430,49 +561,65 @@ window.__ModuleLoader__.load({
430
561
  className: "reflib-addLabel",
431
562
  children: t("add.label")
432
563
  }),
564
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
565
+ className: "reflib-addRow",
566
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
567
+ className: "reflib-addInputWrap",
568
+ value: draft,
569
+ placeholder: t("add.placeholder"),
570
+ "aria-label": t("add.label"),
571
+ disabled: busy || picking,
572
+ onChange: (event) => {
573
+ setDraft(event.currentTarget.value);
574
+ },
575
+ onKeyDown: (event) => {
576
+ if (event.key === "Enter" && !event.nativeEvent.isComposing) {
577
+ event.preventDefault();
578
+ handleSubmit();
579
+ }
580
+ }
581
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
582
+ variant: "outline",
583
+ size: "sm",
584
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 14 }),
585
+ disabled: busy || picking,
586
+ onClick: () => {
587
+ handleBrowse();
588
+ },
589
+ children: t("add.browse")
590
+ })]
591
+ }),
592
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
593
+ className: "reflib-noteWrap",
594
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
595
+ className: "reflib-noteTextarea",
596
+ value: noteDraft,
597
+ placeholder: t("add.note.placeholder"),
598
+ "aria-label": t("add.note.label"),
599
+ disabled: busy || picking,
600
+ rows: 2,
601
+ maxLength: NOTE_MAX_LENGTH,
602
+ onChange: (event) => {
603
+ setNoteDraft(event.currentTarget.value);
604
+ }
605
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
606
+ className: "reflib-noteCount",
607
+ children: [
608
+ noteDraft.length,
609
+ "/",
610
+ NOTE_MAX_LENGTH
611
+ ]
612
+ })]
613
+ }),
433
614
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
434
615
  variant: "primary",
435
- className: "reflib-addBrowseMain",
436
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutline16, { size: 16 }),
437
- disabled: busy || picking,
616
+ className: "reflib-addSubmit",
617
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
618
+ disabled: busy || picking || draft.trim() === "",
438
619
  onClick: () => {
439
- onBrowse();
620
+ handleSubmit();
440
621
  },
441
- children: t("add.browse")
442
- }),
443
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
444
- className: "reflib-addManual",
445
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
446
- className: "reflib-addManualLabel",
447
- children: t("add.manual")
448
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
449
- className: "reflib-addRow",
450
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
451
- className: "reflib-addInputWrap",
452
- value: draft,
453
- placeholder: t("add.placeholder"),
454
- "aria-label": t("add.label"),
455
- disabled: busy || picking,
456
- onChange: (event) => {
457
- setDraft(event.currentTarget.value);
458
- },
459
- onKeyDown: (event) => {
460
- if (event.key === "Enter" && !event.nativeEvent.isComposing) {
461
- event.preventDefault();
462
- handleSubmit();
463
- }
464
- }
465
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
466
- variant: "primary",
467
- size: "sm",
468
- icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, { size: 14 }),
469
- disabled: busy || picking || draft.trim() === "",
470
- onClick: () => {
471
- handleSubmit();
472
- },
473
- children: t("add.submit")
474
- })]
475
- })]
622
+ children: t("add.submit")
476
623
  }),
477
624
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
478
625
  className: "reflib-addHint",
@@ -763,6 +910,12 @@ window.__ModuleLoader__.load({
763
910
  padding: 0 8px;
764
911
  }
765
912
 
913
+ .reflib-listItem {
914
+ display: flex;
915
+ flex-direction: column;
916
+ min-width: 0;
917
+ }
918
+
766
919
  .reflib-row {
767
920
  display: flex;
768
921
  align-items: center;
@@ -778,7 +931,7 @@ window.__ModuleLoader__.load({
778
931
  background: var(--dsw-alias-interactive-bg-hover);
779
932
  }
780
933
 
781
- .reflib-row[data-removing='true'] {
934
+ .reflib-listItem[data-removing='true'] {
782
935
  opacity: 0.55;
783
936
  pointer-events: none;
784
937
  }
@@ -817,6 +970,16 @@ window.__ModuleLoader__.load({
817
970
  white-space: nowrap;
818
971
  }
819
972
 
973
+ /* 列表行用途说明(Description,v8):单行截断,与路径同属次行层级 */
974
+ .reflib-rowNote {
975
+ overflow: hidden;
976
+ color: var(--dsw-alias-label-secondary);
977
+ font-size: 12px;
978
+ line-height: 16px;
979
+ text-overflow: ellipsis;
980
+ white-space: nowrap;
981
+ }
982
+
820
983
  .reflib-rowRemove {
821
984
  display: inline-flex;
822
985
  align-items: center;
@@ -843,6 +1006,120 @@ window.__ModuleLoader__.load({
843
1006
  cursor: default;
844
1007
  }
845
1008
 
1009
+ /* 详情/编辑入口按钮(与移除同尺寸,非危险 hover) */
1010
+ .reflib-rowAction {
1011
+ display: inline-flex;
1012
+ align-items: center;
1013
+ justify-content: center;
1014
+ flex: none;
1015
+ width: 28px;
1016
+ height: 28px;
1017
+ padding: 0;
1018
+ border: none;
1019
+ border-radius: 8px;
1020
+ background: transparent;
1021
+ color: var(--dsw-alias-label-tertiary);
1022
+ cursor: pointer;
1023
+ transition: background-color 120ms ease, color 120ms ease;
1024
+ }
1025
+
1026
+ .reflib-rowAction:hover:not(:disabled) {
1027
+ background: var(--dsw-alias-interactive-bg-hover);
1028
+ color: var(--dsw-alias-label-primary);
1029
+ }
1030
+
1031
+ .reflib-rowAction:disabled {
1032
+ opacity: 0.4;
1033
+ cursor: default;
1034
+ }
1035
+
1036
+ /* 条目详情展开区(ID / 路径 / 用途编辑) */
1037
+ .reflib-detail {
1038
+ display: flex;
1039
+ flex-direction: column;
1040
+ gap: 5px;
1041
+ margin: 2px 0 6px;
1042
+ padding: 8px 10px 10px;
1043
+ border-radius: 10px;
1044
+ background: var(--dsw-alias-surface-inset, var(--dsw-alias-surface-raised));
1045
+ min-width: 0;
1046
+ }
1047
+
1048
+ .reflib-detailMeta {
1049
+ display: flex;
1050
+ align-items: baseline;
1051
+ gap: 8px;
1052
+ min-width: 0;
1053
+ }
1054
+
1055
+ .reflib-detailKey {
1056
+ flex: none;
1057
+ font-size: 11px;
1058
+ line-height: 16px;
1059
+ color: var(--dsw-alias-label-caption);
1060
+ }
1061
+
1062
+ .reflib-detailValue {
1063
+ overflow: hidden;
1064
+ min-width: 0;
1065
+ font-size: 12px;
1066
+ line-height: 16px;
1067
+ color: var(--dsw-alias-label-secondary);
1068
+ text-overflow: ellipsis;
1069
+ white-space: nowrap;
1070
+ }
1071
+
1072
+ .reflib-detailActions {
1073
+ display: flex;
1074
+ justify-content: flex-end;
1075
+ gap: 6px;
1076
+ margin-top: 2px;
1077
+ }
1078
+
1079
+ /* 用途说明多行输入(添加表单 + 详情编辑共用) */
1080
+ .reflib-noteWrap {
1081
+ position: relative;
1082
+ display: flex;
1083
+ min-width: 0;
1084
+ }
1085
+
1086
+ .reflib-noteTextarea {
1087
+ width: 100%;
1088
+ box-sizing: border-box;
1089
+ padding: 6px 8px;
1090
+ border: 1px solid var(--dsw-alias-border-l2);
1091
+ border-radius: 8px;
1092
+ background: var(--dsw-alias-surface-field);
1093
+ color: var(--dsw-alias-label-primary);
1094
+ font: inherit;
1095
+ font-size: 13px;
1096
+ line-height: 18px;
1097
+ resize: vertical;
1098
+ min-height: 40px;
1099
+ }
1100
+
1101
+ .reflib-noteTextarea:focus {
1102
+ outline: 2px solid var(--dsw-alias-focus-ring, var(--dsw-alias-state-info-primary));
1103
+ outline-offset: -1px;
1104
+ }
1105
+
1106
+ .reflib-noteTextarea:disabled {
1107
+ opacity: 0.5;
1108
+ }
1109
+
1110
+ .reflib-noteCount {
1111
+ position: absolute;
1112
+ right: 8px;
1113
+ bottom: 6px;
1114
+ padding: 0 4px;
1115
+ border-radius: 4px;
1116
+ background: var(--dsw-alias-surface-field);
1117
+ font-size: 10px;
1118
+ line-height: 14px;
1119
+ color: var(--dsw-alias-label-caption);
1120
+ pointer-events: none;
1121
+ }
1122
+
846
1123
  /* 空态 / 加载态 / 错误态 */
847
1124
  .reflib-status {
848
1125
  display: flex;
@@ -908,9 +1185,28 @@ window.__ModuleLoader__.load({
908
1185
  background: var(--dsw-alias-border-l2);
909
1186
  }
910
1187
 
1188
+ /* ── 管理面板 Modal(v8 优化)──
1189
+ 宽度放宽到约 1/3 屏(上限 560px);进入动画为柔和缓出(easeOutQuint 风格:
1190
+ 280ms 浮起 + 淡入,位移 12px→0、scale 0.96→1),无回弹——更从容、高级感。 */
1191
+ .reflib-modal {
1192
+ width: min(33vw, 560px);
1193
+ animation: reflib-pop 280ms cubic-bezier(0.16, 1, 0.3, 1);
1194
+ }
1195
+
1196
+ @keyframes reflib-pop {
1197
+ 0% {
1198
+ opacity: 0;
1199
+ transform: scale(0.96) translateY(12px);
1200
+ }
1201
+ 100% {
1202
+ opacity: 1;
1203
+ transform: scale(1) translateY(0);
1204
+ }
1205
+ }
1206
+
911
1207
  /* ── 添加表单 ──
912
- 两组操作分离(优化点 2.1):手动输入路径 + 「添加」是一组(主路径);
913
- 「选择目录」是另一组备选方式,独占一行,避免并排造成困惑。 */
1208
+ 统一表单(v8 UI 重构):路径(可输入 / 浏览填充)+ 用途(可选)同一容器,
1209
+ 同一「添加」按钮提交——用途与路径强关联,浏览不再直接添加。 */
914
1210
  .reflib-add {
915
1211
  display: flex;
916
1212
  flex-direction: column;
@@ -925,25 +1221,6 @@ window.__ModuleLoader__.load({
925
1221
  color: var(--dsw-alias-label-secondary);
926
1222
  }
927
1223
 
928
- /* 主方式:选择目录 —— 全宽主按钮(优先级最高) */
929
- .reflib-addBrowseMain {
930
- width: 100%;
931
- }
932
-
933
- /* 备选:手动输入路径组 */
934
- .reflib-addManual {
935
- display: flex;
936
- flex-direction: column;
937
- gap: 6px;
938
- min-width: 0;
939
- }
940
-
941
- .reflib-addManualLabel {
942
- font-size: 12px;
943
- line-height: 16px;
944
- color: var(--dsw-alias-label-caption);
945
- }
946
-
947
1224
  .reflib-addRow {
948
1225
  display: flex;
949
1226
  align-items: center;
@@ -965,13 +1242,18 @@ window.__ModuleLoader__.load({
965
1242
  line-height: 18px;
966
1243
  }
967
1244
 
1245
+ /* 全宽「添加」主按钮 */
1246
+ .reflib-addSubmit {
1247
+ width: 100%;
1248
+ }
1249
+
968
1250
  .reflib-addHint {
969
1251
  font-size: 11px;
970
1252
  line-height: 16px;
971
1253
  color: var(--dsw-alias-label-caption);
972
1254
  }
973
1255
 
974
- /* 窄视口:主路径行换行,输入框占满一行 */
1256
+ /* 窄视口:路径行换行,输入框占满一行 */
975
1257
  @media (max-width: 440px) {
976
1258
  .reflib-addRow {
977
1259
  flex-wrap: wrap;
@@ -1045,7 +1327,7 @@ window.__ModuleLoader__.load({
1045
1327
  * @returns 胶囊入口(+ 打开时的管理面板)。
1046
1328
  */
1047
1329
  function RefLibDock(props) {
1048
- const { sessionId, load, add, remove, pickDirectory, listDirectory, t } = props;
1330
+ const { sessionId, load, add, remove, setNote, pickDirectory, listDirectory, t } = props;
1049
1331
  const [open, setOpen] = (0, react.useState)(false);
1050
1332
  const [libs, setLibs] = (0, react.useState)([]);
1051
1333
  const [loading, setLoading] = (0, react.useState)(false);
@@ -1056,6 +1338,7 @@ window.__ModuleLoader__.load({
1056
1338
  const seq = (0, react.useRef)(0);
1057
1339
  const [pickerMode, setPickerMode] = (0, react.useState)(null);
1058
1340
  const [browserOpen, setBrowserOpen] = (0, react.useState)(false);
1341
+ const pendingPick = (0, react.useRef)(null);
1059
1342
  const detectPicker = async () => {
1060
1343
  if (pickerMode !== null) return pickerMode;
1061
1344
  let mode;
@@ -1106,11 +1389,11 @@ window.__ModuleLoader__.load({
1106
1389
  setBusy(false);
1107
1390
  }
1108
1391
  };
1109
- const handleAddPath = async (path) => {
1392
+ const handleAddPath = async (path, note) => {
1110
1393
  setBusy(true);
1111
1394
  setError(null);
1112
1395
  try {
1113
- await add(sessionId, path);
1396
+ await add(sessionId, path, note);
1114
1397
  await refresh();
1115
1398
  } catch (cause) {
1116
1399
  setError(formatError(cause, t));
@@ -1119,47 +1402,59 @@ window.__ModuleLoader__.load({
1119
1402
  setBusy(false);
1120
1403
  }
1121
1404
  };
1122
- /** 添加一个已选定目录并刷新列表;返回是否成功(失败时错误已入错误槽,不抛出)。 */
1123
- const addPath = async (path) => {
1405
+ /** 更新条目用途说明(编辑详情保存);失败入错误槽、不抛出(编辑区保持打开)。 */
1406
+ const handleUpdateNote = async (id, note) => {
1124
1407
  setBusy(true);
1125
1408
  setError(null);
1126
1409
  try {
1127
- await add(sessionId, path);
1410
+ await setNote(sessionId, id, note);
1128
1411
  await refresh();
1129
- return true;
1130
1412
  } catch (cause) {
1131
1413
  setError(formatError(cause, t));
1132
- return false;
1133
1414
  } finally {
1134
1415
  setBusy(false);
1135
1416
  }
1136
1417
  };
1137
- const handleBrowse = async () => {
1418
+ /** 唤起目录选择(browse 应用内浏览器 / native 系统对话框)并返回选中的路径;
1419
+ * 取消或失败返回 null——**不直接添加**,由面板把路径填入表单(用户可补用途后提交)。 */
1420
+ const pickPath = async () => {
1138
1421
  setError(null);
1139
1422
  let mode;
1140
1423
  try {
1141
1424
  mode = await detectPicker();
1142
1425
  } catch (cause) {
1143
1426
  setError(formatError(cause, t));
1144
- return;
1427
+ return null;
1145
1428
  }
1146
1429
  if (mode === "browse") {
1147
1430
  setBrowserOpen(true);
1148
- return;
1431
+ return await new Promise((resolve) => {
1432
+ pendingPick.current = resolve;
1433
+ });
1149
1434
  }
1150
1435
  setPicking(true);
1151
1436
  try {
1152
- const picked = await pickDirectory();
1153
- if (picked !== null) await addPath(picked);
1437
+ return await pickDirectory();
1154
1438
  } catch (cause) {
1155
1439
  setError(formatError(cause, t));
1440
+ return null;
1156
1441
  } finally {
1157
1442
  setPicking(false);
1158
1443
  }
1159
1444
  };
1160
- /** 应用内浏览器选定目录 → 添加成功才关闭(失败时错误显示在面板,浏览器保持打开便于重试)。 */
1161
- const handleBrowserPick = async (path) => {
1162
- if (await addPath(path)) setBrowserOpen(false);
1445
+ /** 应用内浏览器选定目录 → 把路径交给等待中的 pickPath 调用方并关闭浏览器。 */
1446
+ const handleBrowserPick = (path) => {
1447
+ setBrowserOpen(false);
1448
+ const resolve = pendingPick.current;
1449
+ pendingPick.current = null;
1450
+ resolve?.(path);
1451
+ };
1452
+ /** 应用内浏览器取消 → 以 null 结束等待中的 pickPath。 */
1453
+ const handleBrowserClose = () => {
1454
+ setBrowserOpen(false);
1455
+ const resolve = pendingPick.current;
1456
+ pendingPick.current = null;
1457
+ resolve?.(null);
1163
1458
  };
1164
1459
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1165
1460
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1207,18 +1502,15 @@ window.__ModuleLoader__.load({
1207
1502
  handleRemove(id);
1208
1503
  },
1209
1504
  onAddPath: handleAddPath,
1210
- onBrowse: handleBrowse
1505
+ onUpdateNote: handleUpdateNote,
1506
+ onBrowse: pickPath
1211
1507
  }),
1212
1508
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RefLibBrowser, {
1213
1509
  open: browserOpen,
1214
- onClose: () => {
1215
- setBrowserOpen(false);
1216
- },
1510
+ onClose: handleBrowserClose,
1217
1511
  listDirectory,
1218
1512
  t,
1219
- onOpen: (path) => {
1220
- handleBrowserPick(path);
1221
- }
1513
+ onOpen: handleBrowserPick
1222
1514
  })
1223
1515
  ] });
1224
1516
  }
@@ -1244,14 +1536,20 @@ window.__ModuleLoader__.load({
1244
1536
  "list.remove": "移除",
1245
1537
  "list.remove.aria": "移除参考库 {name}",
1246
1538
  "list.empty": "还没有参考库",
1247
- "list.empty.hint": "在下方输入目录路径,或点击「选择目录」从系统选择",
1539
+ "list.empty.hint": "在下方输入目录路径,或点击「浏览」选择目录",
1248
1540
  "list.loading": "正在加载…",
1541
+ "detail.open": "查看详情",
1542
+ "detail.open.aria": "查看 {name} 的详情",
1543
+ "detail.path": "路径",
1544
+ "detail.cancel": "取消",
1545
+ "detail.save": "保存",
1249
1546
  "add.label": "添加参考库",
1250
1547
  "add.placeholder": "输入目录路径(支持 ~ 与相对)",
1251
1548
  "add.submit": "添加",
1252
- "add.manual": "或手动输入路径",
1253
- "add.browse": "选择目录",
1254
- "add.hint": "目录仅作只读参考;位于工作区之外时由系统强制只读",
1549
+ "add.browse": "浏览",
1550
+ "add.note.label": "用途说明",
1551
+ "add.note.placeholder": "用途说明(可选,帮助 agent 判断相关性)",
1552
+ "add.hint": "目录仅作只读参考;用途不填时自动提取 README 标题",
1255
1553
  "browser.title": "选择目录",
1256
1554
  "browser.home": "主目录",
1257
1555
  "browser.open": "选择此目录",
@@ -1278,14 +1576,20 @@ window.__ModuleLoader__.load({
1278
1576
  "list.remove": "Remove",
1279
1577
  "list.remove.aria": "Remove reference library {name}",
1280
1578
  "list.empty": "No reference libraries yet",
1281
- "list.empty.hint": "Enter a directory path below, or click “Choose directory” to pick one",
1579
+ "list.empty.hint": "Enter a directory path below, or click “Browse” to pick one",
1282
1580
  "list.loading": "Loading…",
1581
+ "detail.open": "View details",
1582
+ "detail.open.aria": "View details of {name}",
1583
+ "detail.path": "Path",
1584
+ "detail.cancel": "Cancel",
1585
+ "detail.save": "Save",
1283
1586
  "add.label": "Add a reference library",
1284
1587
  "add.placeholder": "Enter a directory path (~ or relative)",
1285
1588
  "add.submit": "Add",
1286
- "add.manual": "or enter a path manually",
1287
- "add.browse": "Choose directory",
1288
- "add.hint": "Directories are read-only references; outside the workspace they are enforced read-only by the system",
1589
+ "add.browse": "Browse",
1590
+ "add.note.label": "Description",
1591
+ "add.note.placeholder": "Description (optional; helps the agent judge relevance)",
1592
+ "add.hint": "Directories are read-only references; if no description is given, the README title is used",
1289
1593
  "browser.title": "Select Directory",
1290
1594
  "browser.home": "Home",
1291
1595
  "browser.open": "Select this folder",
@@ -1344,10 +1648,11 @@ window.__ModuleLoader__.load({
1344
1648
  }), "ref-lib: dictionaries");
1345
1649
  const injected = () => ({
1346
1650
  load: async (sessionId) => parseLibsPayload(await api(`/list?session=${encodeURIComponent(sessionId)}`, void 0, "GET")),
1347
- add: async (sessionId, path) => {
1651
+ add: async (sessionId, path, note) => {
1348
1652
  await api("/add", {
1349
1653
  session: sessionId,
1350
- path
1654
+ path,
1655
+ ...note === void 0 ? {} : { note }
1351
1656
  });
1352
1657
  },
1353
1658
  remove: async (sessionId, id) => {
@@ -1356,6 +1661,13 @@ window.__ModuleLoader__.load({
1356
1661
  id
1357
1662
  });
1358
1663
  },
1664
+ setNote: async (sessionId, id, note) => {
1665
+ await api("/note", {
1666
+ session: sessionId,
1667
+ id,
1668
+ note
1669
+ });
1670
+ },
1359
1671
  pickDirectory: () => ctx.workspaces.pickDirectory(),
1360
1672
  listDirectory: (path, signal) => ctx.workspaces.listDirectory(path, signal)
1361
1673
  });