@roaming-ai/dsh-group-chat 0.3.0 → 0.3.2

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 (80) hide show
  1. package/README.md +93 -28
  2. package/lib/client.js +295 -326
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +234 -221
  5. package/lib/types/client/components/AsidePanel.d.ts +3 -19
  6. package/lib/types/client/components/NavPanel.d.ts +9 -11
  7. package/lib/types/client/components/RoleDrawer.d.ts +3 -2
  8. package/lib/types/client/components/ToolRow.d.ts +2 -2
  9. package/lib/types/client/hooks/useGroupChatState.d.ts +0 -16
  10. package/lib/types/client/lib/model.d.ts +1 -7
  11. package/lib/types/client/utils/utils.d.ts +4 -4
  12. package/lib/types/core/json.d.ts +6 -0
  13. package/lib/types/core/types.d.ts +59 -64
  14. package/lib/types/host/engine/defaults.d.ts +13 -0
  15. package/lib/types/host/persistence/persistence.d.ts +2 -4
  16. package/lib/types/host/persistence/store.d.ts +5 -1
  17. package/lib/types/host/service.d.ts +2 -2
  18. package/lib/types/host/state.d.ts +1 -0
  19. package/lib/types/host/tools/tools.d.ts +2 -1
  20. package/lib/types/index.d.ts +3 -0
  21. package/lib/types/shared/file-mention-grammar.d.ts +3 -12
  22. package/package.json +4 -1
  23. package/src/client/GroupChatPanel.tsx +9 -56
  24. package/src/client/components/AsidePanel.tsx +40 -15
  25. package/src/client/components/ChatPanel.tsx +1 -1
  26. package/src/client/components/Composer.tsx +2 -2
  27. package/src/client/components/ConstraintList.tsx +1 -6
  28. package/src/client/components/MessageFlow.tsx +2 -1
  29. package/src/client/components/NavPanel.tsx +96 -78
  30. package/src/client/components/RoleDrawer.tsx +29 -7
  31. package/src/client/components/ToolRow.tsx +2 -2
  32. package/src/client/hooks/useComposer.ts +14 -53
  33. package/src/client/hooks/useGroupChatState.ts +2 -13
  34. package/src/client/lib/model.ts +1 -14
  35. package/src/client/lib/styles.ts +6 -2
  36. package/src/client/utils/utils.ts +6 -6
  37. package/src/core/constraints.ts +6 -12
  38. package/src/core/errors.ts +2 -12
  39. package/src/core/json.ts +17 -0
  40. package/src/core/types.ts +29 -20
  41. package/src/host/api/actions.ts +14 -13
  42. package/src/host/engine/conversation.ts +50 -51
  43. package/src/host/engine/defaults.ts +28 -0
  44. package/src/host/engine/fold.ts +2 -17
  45. package/src/host/engine/retitle.ts +9 -31
  46. package/src/host/materials/materials.ts +2 -0
  47. package/src/host/persistence/persistence.ts +57 -34
  48. package/src/host/persistence/store.ts +5 -1
  49. package/src/host/service.ts +4 -4
  50. package/src/host/state.ts +5 -2
  51. package/src/host/tools/tools.ts +42 -48
  52. package/src/index.ts +7 -5
  53. package/src/shared/file-mention-grammar.test.ts +1 -38
  54. package/src/shared/file-mention-grammar.ts +7 -33
  55. package/lib/types/client/Bubble.d.ts +0 -10
  56. package/lib/types/client/Glyph.d.ts +0 -12
  57. package/lib/types/client/GroupChatPanel.old.d.ts +0 -10
  58. package/lib/types/client/GroupChatSettingsSection.d.ts +0 -15
  59. package/lib/types/client/PermissionSelect.d.ts +0 -13
  60. package/lib/types/client/RoleDrawer.d.ts +0 -17
  61. package/lib/types/client/ThinkRow.d.ts +0 -9
  62. package/lib/types/client/ToolRow.d.ts +0 -9
  63. package/lib/types/client/api.d.ts +0 -8
  64. package/lib/types/client/components.d.ts +0 -22
  65. package/lib/types/client/drawer.d.ts +0 -17
  66. package/lib/types/client/glyph.d.ts +0 -12
  67. package/lib/types/client/model.d.ts +0 -78
  68. package/lib/types/client/panel.d.ts +0 -10
  69. package/lib/types/client/settings.d.ts +0 -15
  70. package/lib/types/client/styles.d.ts +0 -10
  71. package/lib/types/client/ui.d.ts +0 -17
  72. package/lib/types/client/utils.d.ts +0 -22
  73. package/lib/types/host/actions.d.ts +0 -34
  74. package/lib/types/host/conversation.d.ts +0 -25
  75. package/lib/types/host/http.d.ts +0 -16
  76. package/lib/types/host/materials.d.ts +0 -32
  77. package/lib/types/host/persistence.d.ts +0 -31
  78. package/lib/types/host/routes.d.ts +0 -11
  79. package/lib/types/host/store.d.ts +0 -65
  80. package/lib/types/host/tools.d.ts +0 -28
package/lib/client.js CHANGED
@@ -197,8 +197,8 @@ window.__ModuleLoader__.load({
197
197
  ".dsgc-ph{position:absolute;left:14px;top:6px;max-width:calc(100% - 22px);color:var(--dsw-alias-label-tertiary,inherit);pointer-events:none;user-select:none;font-size:13px;line-height:24px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}",
198
198
  ".dsgc-chipin{display:inline-flex;align-items:center;gap:4px;background:color-mix(in srgb,var(--role-color,#888) 15%,transparent);border-radius:999px;padding:1px 7px 1px 5px;margin:0 1px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary,inherit);white-space:nowrap;user-select:all}",
199
199
  ".dsgc-card .dsgc-sendrow{padding:0 12px 10px}",
200
- ".dsgc-stopbtn{background:var(--dsw-alias-state-error-primary,#e5484d);border-color:transparent;color:#fff;font-weight:600}",
201
- ".dsgc-stopbtn:hover:not(:disabled){filter:brightness(1.08);color:#fff}",
200
+ "button.dsgc-stopbtn{background:var(--dsw-alias-state-error-primary,#e5484d);border-color:transparent;color:#fff;font-weight:600}",
201
+ "button.dsgc-stopbtn:hover:not(:disabled){background:var(--dsw-alias-state-error-primary,#e5484d);filter:brightness(1.08);color:#fff}",
202
202
  ".dsgc-parts{display:flex;align-items:flex-start;gap:6px;min-height:24px}",
203
203
  ".dsgc-partslabel{font-size:12px;line-height:24px;color:var(--dsw-alias-label-tertiary,inherit);flex:none}",
204
204
  ".dsgc-partlist{display:flex;flex-wrap:wrap;align-items:center;gap:6px;row-gap:4px;flex:1;min-width:0}",
@@ -492,14 +492,17 @@ window.__ModuleLoader__.load({
492
492
  * @module dsh-group-chat/client/drawer
493
493
  */
494
494
  function RoleDrawer(props) {
495
- const { draft, set } = props;
495
+ const { draft, set, groupId } = props;
496
496
  const models = props.models;
497
497
  const providers = models && models.providers || [];
498
498
  const modelsOf = models && models.modelsByProvider && models.modelsByProvider[draft.provider] || [];
499
499
  const [closing, setClosing] = (0, react.useState)(false);
500
+ const closingRef = (0, react.useRef)(false);
501
+ const [formError, setFormError] = (0, react.useState)("");
500
502
  const [effortsInfo, setEffortsInfo] = (0, react.useState)(null);
501
503
  const close = () => {
502
- if (closing) return;
504
+ if (closingRef.current) return;
505
+ closingRef.current = true;
503
506
  setClosing(true);
504
507
  window.setTimeout(() => props.onCancel(), 140);
505
508
  };
@@ -512,6 +515,23 @@ window.__ModuleLoader__.load({
512
515
  reasoningEffort: "default"
513
516
  }));
514
517
  };
518
+ /** 保存(校验在本地,落库经 mutate;失败文案与原实现逐字一致)。 */
519
+ const save = async () => {
520
+ if (!draft.name.trim()) {
521
+ setFormError("角色名称不能为空");
522
+ return;
523
+ }
524
+ if (!draft.provider || !draft.model) {
525
+ setFormError("请选择角色绑定的模型");
526
+ return;
527
+ }
528
+ const res = await props.mutate({
529
+ op: "upsertRole",
530
+ groupId,
531
+ role: draft
532
+ });
533
+ if (res && res.ok && res.snapshot && !res.snapshot.error) props.onCancel();
534
+ };
515
535
  (0, react.useEffect)(() => {
516
536
  const onKey = (e) => {
517
537
  if (e.key === "Escape") close();
@@ -729,9 +749,9 @@ window.__ModuleLoader__.load({
729
749
  children: "当前模型未提供推理等级,将使用默认"
730
750
  })]
731
751
  }) : null,
732
- props.formError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
752
+ formError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
733
753
  className: "dsgc-formerr",
734
- children: props.formError
754
+ children: formError
735
755
  }) : null
736
756
  ]
737
757
  })
@@ -744,7 +764,9 @@ window.__ModuleLoader__.load({
744
764
  children: "取消"
745
765
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
746
766
  variant: "primary",
747
- onClick: props.onSave,
767
+ onClick: () => {
768
+ save();
769
+ },
748
770
  children: draft.id ? "保存" : "添加"
749
771
  })]
750
772
  })
@@ -866,6 +888,55 @@ window.__ModuleLoader__.load({
866
888
  }
867
889
  //#endregion
868
890
  //#region src/client/components/NavPanel.tsx
891
+ /** 内联重命名输入(群组行/会话行同款)。 */
892
+ function RenameField(props) {
893
+ const { draft, set, commit, keyDown } = props;
894
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
895
+ className: "dsgc-rename",
896
+ value: draft.value,
897
+ autoFocus: true,
898
+ onChange: (e) => {
899
+ set({
900
+ kind: draft.kind,
901
+ id: draft.id,
902
+ value: e.target.value
903
+ });
904
+ },
905
+ onBlur: () => {
906
+ commit();
907
+ },
908
+ onKeyDown: keyDown,
909
+ onClick: (e) => {
910
+ e.stopPropagation();
911
+ }
912
+ });
913
+ }
914
+ /** 行尾操作钮:重命名 + 两次点击确认删除(群组行/会话行同款)。 */
915
+ function NodeOps(props) {
916
+ const { renameLabel, deleteLabel, deleteTitle, danger, onRename, onDelete } = props;
917
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
918
+ className: "dsgc-nodeops",
919
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
920
+ className: "dsgc-opbtn",
921
+ title: renameLabel,
922
+ "aria-label": renameLabel,
923
+ onClick: (e) => {
924
+ e.stopPropagation();
925
+ onRename();
926
+ },
927
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, 14)
928
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
929
+ className: "dsgc-opbtn" + (danger ? " danger" : ""),
930
+ title: deleteTitle,
931
+ "aria-label": deleteLabel,
932
+ onClick: (e) => {
933
+ e.stopPropagation();
934
+ onDelete();
935
+ },
936
+ children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, 14)
937
+ })]
938
+ });
939
+ }
869
940
  function NavPanel(props) {
870
941
  const { snap, search, setSearch, collapsedGroups, setCollapsedGroups, gid, sid, setGid, setSid, setPartsSel, renameDraft, setRenameDraft, confirmDel, setConfirmDel, mutate, navOpen } = props;
871
942
  const commitRename = async () => {
@@ -907,6 +978,20 @@ window.__ModuleLoader__.load({
907
978
  sessionId: d.id
908
979
  });
909
980
  };
981
+ const clearEdits = () => {
982
+ setPartsSel(null);
983
+ setConfirmDel(null);
984
+ setRenameDraft(null);
985
+ };
986
+ const pickGroup = (id) => {
987
+ setGid(id);
988
+ clearEdits();
989
+ };
990
+ const pickSession = (gidToSet, sidToSet) => {
991
+ setGid(gidToSet);
992
+ setSid(sidToSet);
993
+ clearEdits();
994
+ };
910
995
  const q = search.trim().toLowerCase();
911
996
  const groupMatches = (g) => {
912
997
  if (!q) return {
@@ -923,6 +1008,7 @@ window.__ModuleLoader__.load({
923
1008
  };
924
1009
  };
925
1010
  const group = gid ? groupById(snap, gid) : null;
1011
+ const selected = sid ? sessById(snap, sid) : null;
926
1012
  const treeNodes = [];
927
1013
  for (const g of snap.groups) {
928
1014
  const match = groupMatches(g);
@@ -936,8 +1022,7 @@ window.__ModuleLoader__.load({
936
1022
  const s = sessById(snap, sessionId);
937
1023
  if (!s) continue;
938
1024
  if (match.filterSessions && !s.name.toLowerCase().includes(q)) continue;
939
- const sess = sid ? sessById(snap, sid) : null;
940
- const isActive = sess && s.id === sess.id && g.id === group?.id;
1025
+ const isActive = selected && s.id === selected.id && g.id === group?.id;
941
1026
  const isRenameSess = renameDraft && renameDraft.kind === "session" && renameDraft.id === s.id;
942
1027
  const isConfirm = confirmDel && confirmDel.kind === "session" && confirmDel.id === s.id;
943
1028
  const st = sessStatus(snap.run, s.id);
@@ -946,20 +1031,12 @@ window.__ModuleLoader__.load({
946
1031
  role: "button",
947
1032
  tabIndex: 0,
948
1033
  onClick: () => {
949
- setGid(g.id);
950
- setSid(s.id);
951
- setPartsSel(null);
952
- setConfirmDel(null);
953
- setRenameDraft(null);
1034
+ pickSession(g.id, s.id);
954
1035
  },
955
1036
  onKeyDown: (e) => {
956
1037
  if (e.key === "Enter" || e.key === " ") {
957
1038
  e.preventDefault();
958
- setGid(g.id);
959
- setSid(s.id);
960
- setPartsSel(null);
961
- setConfirmDel(null);
962
- setRenameDraft(null);
1039
+ pickSession(g.id, s.id);
963
1040
  }
964
1041
  },
965
1042
  children: [
@@ -975,24 +1052,13 @@ window.__ModuleLoader__.load({
975
1052
  })
976
1053
  }) : null
977
1054
  }),
978
- isRenameSess ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
979
- className: "dsgc-rename",
980
- value: renameDraft.value,
981
- autoFocus: true,
982
- onChange: (e) => {
983
- setRenameDraft({
984
- kind: "session",
985
- id: s.id,
986
- value: e.target.value
987
- });
988
- },
989
- onBlur: () => {
1055
+ isRenameSess ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenameField, {
1056
+ draft: renameDraft,
1057
+ set: setRenameDraft,
1058
+ commit: () => {
990
1059
  commitRename();
991
1060
  },
992
- onKeyDown: renameKeyDown,
993
- onClick: (e) => {
994
- e.stopPropagation();
995
- }
1061
+ keyDown: renameKeyDown
996
1062
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HoverTip, {
997
1063
  label: s.name,
998
1064
  side: "right",
@@ -1000,35 +1066,25 @@ window.__ModuleLoader__.load({
1000
1066
  className: "dsgc-sess-name",
1001
1067
  children: s.name
1002
1068
  }),
1003
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1004
- className: "dsgc-nodeops",
1005
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1006
- className: "dsgc-opbtn",
1007
- title: "重命名会话",
1008
- "aria-label": "重命名会话",
1009
- onClick: (e) => {
1010
- e.stopPropagation();
1011
- setRenameDraft({
1012
- kind: "session",
1013
- id: s.id,
1014
- value: s.name
1015
- });
1016
- },
1017
- children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, 14)
1018
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1019
- className: "dsgc-opbtn" + (isConfirm ? " danger" : ""),
1020
- title: isConfirm ? "再次点击确认删除" : "删除会话",
1021
- "aria-label": "删除会话",
1022
- onClick: (e) => {
1023
- e.stopPropagation();
1024
- if (isConfirm) doDelete();
1025
- else setConfirmDel({
1026
- kind: "session",
1027
- id: s.id
1028
- });
1029
- },
1030
- children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, 14)
1031
- })]
1069
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeOps, {
1070
+ renameLabel: "重命名会话",
1071
+ deleteLabel: "删除会话",
1072
+ deleteTitle: isConfirm ? "再次点击确认删除" : "删除会话",
1073
+ danger: !!isConfirm,
1074
+ onRename: () => {
1075
+ setRenameDraft({
1076
+ kind: "session",
1077
+ id: s.id,
1078
+ value: s.name
1079
+ });
1080
+ },
1081
+ onDelete: () => {
1082
+ if (isConfirm) doDelete();
1083
+ else setConfirmDel({
1084
+ kind: "session",
1085
+ id: s.id
1086
+ });
1087
+ }
1032
1088
  })
1033
1089
  ]
1034
1090
  }, s.id));
@@ -1051,18 +1107,12 @@ window.__ModuleLoader__.load({
1051
1107
  role: "button",
1052
1108
  tabIndex: 0,
1053
1109
  onClick: () => {
1054
- setGid(g.id);
1055
- setPartsSel(null);
1056
- setConfirmDel(null);
1057
- setRenameDraft(null);
1110
+ pickGroup(g.id);
1058
1111
  },
1059
1112
  onKeyDown: (e) => {
1060
1113
  if (e.key === "Enter" || e.key === " ") {
1061
1114
  e.preventDefault();
1062
- setGid(g.id);
1063
- setPartsSel(null);
1064
- setConfirmDel(null);
1065
- setRenameDraft(null);
1115
+ pickGroup(g.id);
1066
1116
  }
1067
1117
  },
1068
1118
  children: [
@@ -1081,57 +1131,36 @@ window.__ModuleLoader__.load({
1081
1131
  },
1082
1132
  children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, 14)
1083
1133
  }),
1084
- isRenameGroup ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1085
- className: "dsgc-rename",
1086
- value: renameDraft.value,
1087
- autoFocus: true,
1088
- onChange: (e) => {
1089
- setRenameDraft({
1090
- kind: "group",
1091
- id: g.id,
1092
- value: e.target.value
1093
- });
1094
- },
1095
- onBlur: () => {
1134
+ isRenameGroup ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenameField, {
1135
+ draft: renameDraft,
1136
+ set: setRenameDraft,
1137
+ commit: () => {
1096
1138
  commitRename();
1097
1139
  },
1098
- onKeyDown: renameKeyDown,
1099
- onClick: (e) => {
1100
- e.stopPropagation();
1101
- }
1140
+ keyDown: renameKeyDown
1102
1141
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1103
1142
  className: "dsgc-gname",
1104
1143
  children: g.name
1105
1144
  }),
1106
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1107
- className: "dsgc-nodeops",
1108
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1109
- className: "dsgc-opbtn",
1110
- title: "重命名群组",
1111
- "aria-label": "重命名群组",
1112
- onClick: (e) => {
1113
- e.stopPropagation();
1114
- setRenameDraft({
1115
- kind: "group",
1116
- id: g.id,
1117
- value: g.name
1118
- });
1119
- },
1120
- children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, 14)
1121
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1122
- className: "dsgc-opbtn" + (isConfirmGroup ? " danger" : ""),
1123
- title: isConfirmGroup ? "再次点击确认删除" : "删除群组",
1124
- "aria-label": "删除群组",
1125
- onClick: (e) => {
1126
- e.stopPropagation();
1127
- if (isConfirmGroup) doDelete();
1128
- else setConfirmDel({
1129
- kind: "group",
1130
- id: g.id
1131
- });
1132
- },
1133
- children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, 14)
1134
- })]
1145
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeOps, {
1146
+ renameLabel: "重命名群组",
1147
+ deleteLabel: "删除群组",
1148
+ deleteTitle: isConfirmGroup ? "再次点击确认删除" : "删除群组",
1149
+ danger: !!isConfirmGroup,
1150
+ onRename: () => {
1151
+ setRenameDraft({
1152
+ kind: "group",
1153
+ id: g.id,
1154
+ value: g.name
1155
+ });
1156
+ },
1157
+ onDelete: () => {
1158
+ if (isConfirmGroup) doDelete();
1159
+ else setConfirmDel({
1160
+ kind: "group",
1161
+ id: g.id
1162
+ });
1163
+ }
1135
1164
  })
1136
1165
  ]
1137
1166
  }), expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1170,8 +1199,14 @@ window.__ModuleLoader__.load({
1170
1199
  }
1171
1200
  //#endregion
1172
1201
  //#region src/client/components/AsidePanel.tsx
1202
+ /**
1203
+ * 右侧成员与工作区栏组件。工作区目录编辑态(草稿、文件浏览器)为栏内自有状态。
1204
+ * @module dsh-group-chat/client/components
1205
+ */
1173
1206
  function AsidePanel(props) {
1174
- const { snap, group, asideOpen, wsDraft, setWsDraft, fileBrowser, setFileBrowser, mutate, openRoleEditor, openBrowser, selectCurrentDir } = props;
1207
+ const { snap, group, asideOpen, mutate, openRoleEditor } = props;
1208
+ const [wsDraft, setWsDraft] = (0, react.useState)(null);
1209
+ const [fileBrowser, setFileBrowser] = (0, react.useState)(null);
1175
1210
  const commitWsDir = () => {
1176
1211
  if (wsDraft !== null && group && wsDraft !== (group.workspaceDir || "")) mutate({
1177
1212
  op: "setWorkspaceDir",
@@ -1180,6 +1215,50 @@ window.__ModuleLoader__.load({
1180
1215
  });
1181
1216
  setWsDraft(null);
1182
1217
  };
1218
+ const openBrowser = async (path) => {
1219
+ setFileBrowser({
1220
+ open: true,
1221
+ loading: true,
1222
+ list: null,
1223
+ error: ""
1224
+ });
1225
+ try {
1226
+ const res = await api.action({
1227
+ kind: "browse",
1228
+ path: path || ""
1229
+ });
1230
+ if (res && res.ok) setFileBrowser({
1231
+ open: true,
1232
+ loading: false,
1233
+ list: res,
1234
+ error: ""
1235
+ });
1236
+ else setFileBrowser({
1237
+ open: true,
1238
+ loading: false,
1239
+ list: null,
1240
+ error: res && res.error || "浏览失败"
1241
+ });
1242
+ } catch (e) {
1243
+ setFileBrowser({
1244
+ open: true,
1245
+ loading: false,
1246
+ list: null,
1247
+ error: String(e && e.message || e)
1248
+ });
1249
+ }
1250
+ };
1251
+ const selectCurrentDir = () => {
1252
+ if (!fileBrowser || !fileBrowser.list || !group) return;
1253
+ const path = fileBrowser.list.path;
1254
+ mutate({
1255
+ op: "setWorkspaceDir",
1256
+ groupId: group.id,
1257
+ path
1258
+ });
1259
+ setWsDraft(null);
1260
+ setFileBrowser(null);
1261
+ };
1183
1262
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1184
1263
  className: "dsgc-aside" + (asideOpen ? "" : " closed"),
1185
1264
  "aria-hidden": asideOpen ? void 0 : "true",
@@ -1455,6 +1534,24 @@ window.__ModuleLoader__.load({
1455
1534
  });
1456
1535
  }
1457
1536
  //#endregion
1537
+ //#region src/core/json.ts
1538
+ /**
1539
+ * 宽容 JSON 提取(模型输出/供应商错误原文的统一解析入口):
1540
+ * 取首个 { 至末个 } 的片段解析,仅接受普通对象;失败返回 null。
1541
+ * 代码围栏剥离由调用方按需先行处理(供应商错误原文不剥)。
1542
+ */
1543
+ function looseJson(raw) {
1544
+ const start = raw.indexOf("{");
1545
+ const end = raw.lastIndexOf("}");
1546
+ if (start < 0 || end <= start) return null;
1547
+ try {
1548
+ const parsed = JSON.parse(raw.slice(start, end + 1));
1549
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
1550
+ } catch {
1551
+ return null;
1552
+ }
1553
+ }
1554
+ //#endregion
1458
1555
  //#region src/core/types.ts
1459
1556
  /** 全部合法档位(展示顺序)。 */
1460
1557
  const PERMISSION_TIERS = [
@@ -1467,6 +1564,13 @@ window.__ModuleLoader__.load({
1467
1564
  return typeof value === "string" && PERMISSION_TIERS.includes(value) ? value : void 0;
1468
1565
  }
1469
1566
  //#endregion
1567
+ //#region src/core/constraints.ts
1568
+ const KIND_LABEL = {
1569
+ decided: "已定",
1570
+ rejected: "否决",
1571
+ open: "未决"
1572
+ };
1573
+ //#endregion
1470
1574
  //#region src/core/errors.ts
1471
1575
  const PREFIX = "模型输出异常终止: ";
1472
1576
  const LEGACY_ROLE = /^角色「([^」]+)」发言失败[::]\s*/;
@@ -1504,17 +1608,6 @@ window.__ModuleLoader__.load({
1504
1608
  const hits = roles.filter((r) => r.name === parsed.roleName);
1505
1609
  return hits.length === 1 ? hits[0] : null;
1506
1610
  }
1507
- function pickJsonBlob(text) {
1508
- const start = text.indexOf("{");
1509
- const end = text.lastIndexOf("}");
1510
- if (start < 0 || end <= start) return null;
1511
- try {
1512
- const parsed = JSON.parse(text.slice(start, end + 1));
1513
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
1514
- } catch {
1515
- return null;
1516
- }
1517
- }
1518
1611
  function httpStatus(text) {
1519
1612
  const m = text.match(/\b([1-5]\d{2})\b/);
1520
1613
  if (!m) return null;
@@ -1531,7 +1624,7 @@ window.__ModuleLoader__.load({
1531
1624
  */
1532
1625
  function classifySpeakFailure(raw) {
1533
1626
  const source = unwrapSpeakFailure(raw);
1534
- const blob = pickJsonBlob(source);
1627
+ const blob = looseJson(source);
1535
1628
  const code = blob && typeof blob.code === "string" ? blob.code : "";
1536
1629
  const type = blob && typeof blob.type === "string" ? blob.type : "";
1537
1630
  const message = blob && typeof blob.message === "string" ? blob.message : source;
@@ -1974,11 +2067,6 @@ window.__ModuleLoader__.load({
1974
2067
  * 会话流折点处:只读结论备忘卡(超过 4 条默认露 3 条)。
1975
2068
  * @module dsh-group-chat/client/components
1976
2069
  */
1977
- const KIND_LABEL = {
1978
- decided: "已定",
1979
- rejected: "否决",
1980
- open: "未决"
1981
- };
1982
2070
  function ConstraintRow(props) {
1983
2071
  const { item } = props;
1984
2072
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -2154,6 +2242,7 @@ window.__ModuleLoader__.load({
2154
2242
  }, "__confirm") : null;
2155
2243
  let livePlaced = false;
2156
2244
  if (sess) {
2245
+ const groupRoles = snap.roles.filter((r) => r.groupId === sess.groupId);
2157
2246
  const ids = sess.messageIds;
2158
2247
  const memo = sess.constraints && sess.constraints.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConstraintList, { items: sess.constraints }, sess.id + "-constraints") : null;
2159
2248
  const foldAt = memo && ids.length > 40 ? ids.length - 40 : 0;
@@ -2162,7 +2251,7 @@ window.__ModuleLoader__.load({
2162
2251
  if (memo && foldAt > 0 && i === foldAt) bubbles.push(memo);
2163
2252
  const m = msgById[ids[i]];
2164
2253
  if (!m) continue;
2165
- const role = resolveFailedRole(m, snap.roles.filter((r) => r.groupId === sess.groupId)) || (m.speaker !== "user" && m.speaker !== "system" ? roleById(snap, m.speaker) : null);
2254
+ const role = resolveFailedRole(m, groupRoles) || (m.speaker !== "user" && m.speaker !== "system" ? roleById(snap, m.speaker) : null);
2166
2255
  if (replaceId && m.id === replaceId) {
2167
2256
  if (live) bubbles.push(live);
2168
2257
  if (confirm) bubbles.push(confirm);
@@ -2526,7 +2615,7 @@ window.__ModuleLoader__.load({
2526
2615
  !input.trim() ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2527
2616
  className: "dsgc-ph",
2528
2617
  "aria-hidden": "true",
2529
- children: "发消息给全群,@成员 点名让其回应(留空则让角色自由讨论)…"
2618
+ children: "发消息给全群,@成员 点名让其回应…"
2530
2619
  }) : null,
2531
2620
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2532
2621
  className: "dsgc-edit",
@@ -2609,7 +2698,7 @@ window.__ModuleLoader__.load({
2609
2698
  onClick: () => {
2610
2699
  sendMsg();
2611
2700
  },
2612
- disabled: !participants.length && !mentionedRoles.length || !sess,
2701
+ disabled: !input.trim() || !participants.length && !mentionedRoles.length || !sess,
2613
2702
  children: [Icon(_deepseek_ai_dsh_client_ui_primitives.IconSendOutline16, 16), "发送"]
2614
2703
  })
2615
2704
  ]
@@ -2712,7 +2801,7 @@ window.__ModuleLoader__.load({
2712
2801
  }),
2713
2802
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2714
2803
  className: "dsgc-hint",
2715
- children: enabledRoles.length ? "发送消息开始讨论;@成员 点名让其优先回应;留空直接发送可让角色自由讨论——每轮全体参与角色按顺序各发言一次,可用右下角轮数控制(1–10 轮)" : "先在右侧添加角色(每个角色可绑定不同模型),再回到这里发起讨论。"
2804
+ children: enabledRoles.length ? "发送消息开始讨论;@成员 点名让其优先回应——每轮全体参与角色按顺序各发言一次,可用右下角轮数控制(1–10 轮)" : "先在右侧添加角色(每个角色可绑定不同模型),再回到这里发起讨论。"
2716
2805
  })
2717
2806
  ]
2718
2807
  })
@@ -2835,12 +2924,9 @@ window.__ModuleLoader__.load({
2835
2924
  const [confirmDel, setConfirmDel] = (0, react.useState)(null);
2836
2925
  const [confirmClear, setConfirmClear] = (0, react.useState)(false);
2837
2926
  const [topicDraft, setTopicDraft] = (0, react.useState)(null);
2838
- const [wsDraft, setWsDraft] = (0, react.useState)(null);
2839
2927
  const [roleDraft, setRoleDraft] = (0, react.useState)(null);
2840
- const [roleFormError, setRoleFormError] = (0, react.useState)("");
2841
2928
  const [models, setModels] = (0, react.useState)(null);
2842
2929
  const [modelsError, setModelsError] = (0, react.useState)(null);
2843
- const [fileBrowser, setFileBrowser] = (0, react.useState)(null);
2844
2930
  const [partsSel, setPartsSel] = (0, react.useState)(null);
2845
2931
  const [rounds, setRounds] = (0, react.useState)(1);
2846
2932
  const [input, setInput] = (0, react.useState)(() => readComposerDraft(cachedSid).text);
@@ -2920,16 +3006,10 @@ window.__ModuleLoader__.load({
2920
3006
  setConfirmClear,
2921
3007
  roleDraft,
2922
3008
  setRoleDraft,
2923
- roleFormError,
2924
- setRoleFormError,
2925
3009
  models,
2926
3010
  setModels,
2927
3011
  modelsError,
2928
3012
  setModelsError,
2929
- fileBrowser,
2930
- setFileBrowser,
2931
- wsDraft,
2932
- setWsDraft,
2933
3013
  partsSel,
2934
3014
  setPartsSel,
2935
3015
  rounds,
@@ -2962,7 +3042,7 @@ window.__ModuleLoader__.load({
2962
3042
  /**
2963
3043
  * Extract active @token at cursor position.
2964
3044
  * Matches @ at word boundary (line start or after whitespace).
2965
- *
3045
+ *
2966
3046
  * @param line - Current line text
2967
3047
  * @param cursor - Cursor offset in line
2968
3048
  * @returns Token or undefined if no active @token at cursor
@@ -3020,10 +3100,10 @@ window.__ModuleLoader__.load({
3020
3100
  };
3021
3101
  return Array.from(root.childNodes).map(walk).join("");
3022
3102
  }
3023
- /** @角色芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删)。 */
3024
- function chipHtml(role) {
3103
+ /** @角色芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删;fresh=插入后需营救选区,加 data-new 标记)。 */
3104
+ function chipHtml(role, fresh = false) {
3025
3105
  const c = escapeHtml(role.color || "#888");
3026
- return "<span class=\"dsgc-chipin\" data-role-id=\"" + escapeHtml(role.id) + "\" data-name=\"" + escapeHtml(role.name) + "\" style=\"--role-color:" + c + "\" contenteditable=\"false\" draggable=\"true\"><span class=\"dsgc-chipdot\" style=\"background:" + c + "\"></span>" + escapeHtml(role.name) + "</span>";
3106
+ return "<span class=\"dsgc-chipin\"" + (fresh ? " data-new=\"\"" : "") + " data-role-id=\"" + escapeHtml(role.id) + "\" data-name=\"" + escapeHtml(role.name) + "\" style=\"--role-color:" + c + "\" contenteditable=\"false\" draggable=\"true\"><span class=\"dsgc-chipdot\" style=\"background:" + c + "\"></span>" + escapeHtml(role.name) + "</span>";
3027
3107
  }
3028
3108
  /** 把宿主 FileTypeIcon 渲成静态 SVG,再塞进 insertHTML(与检索列表同一套字形)。 */
3029
3109
  function fileTypeIconMarkup(path, kind) {
@@ -3045,10 +3125,10 @@ window.__ModuleLoader__.load({
3045
3125
  root.unmount();
3046
3126
  }
3047
3127
  }
3048
- /** @文件芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删)。 */
3049
- function fileChipHtml(path, kind) {
3128
+ /** @文件芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删;fresh 同 chipHtml)。 */
3129
+ function fileChipHtml(path, kind, fresh = false) {
3050
3130
  const basename = path.split("/").pop() || path;
3051
- return "<span class=\"dsgc-chipin dsgc-chipin-file\" data-kind=\"file\"" + (kind === "directory" ? " data-dir=\"1\"" : "") + " data-path=\"" + escapeHtml(path) + "\" contenteditable=\"false\" draggable=\"true\"><span class=\"dsgc-chipglyph\" aria-hidden=\"true\">" + fileTypeIconMarkup(path, kind) + "</span>" + escapeHtml(basename) + "</span>";
3131
+ return "<span class=\"dsgc-chipin dsgc-chipin-file\"" + (fresh ? " data-new=\"\"" : "") + " data-kind=\"file\"" + (kind === "directory" ? " data-dir=\"1\"" : "") + " data-path=\"" + escapeHtml(path) + "\" contenteditable=\"false\" draggable=\"true\"><span class=\"dsgc-chipglyph\" aria-hidden=\"true\">" + fileTypeIconMarkup(path, kind) + "</span>" + escapeHtml(basename) + "</span>";
3052
3132
  }
3053
3133
  /**
3054
3134
  * 光标前的活跃 @token(角色或文件弹层触发判定)。
@@ -3158,97 +3238,59 @@ window.__ModuleLoader__.load({
3158
3238
  ]);
3159
3239
  }
3160
3240
  function useMentionChip(inputRef, setMention, setMentionIdx, syncFromDOM) {
3161
- return {
3162
- insertChip: (0, react.useCallback)((role) => {
3163
- const el = inputRef.current;
3164
- const sel = window.getSelection();
3165
- if (!el || !sel) return;
3166
- if (!sel.anchorNode || !el.contains(sel.anchorNode)) return;
3167
- el.focus({ preventScroll: true });
3168
- const node = sel.anchorNode;
3169
- if (node.nodeType === Node.TEXT_NODE) {
3170
- const text = node.nodeValue || "";
3171
- const off = sel.anchorOffset;
3172
- const before = text.slice(0, off);
3173
- const m = /(?:^|\s)@("?)([^"\s@]*)$/.exec(before);
3174
- if (m) {
3175
- const prefixLen = m[1] ? 2 : 1;
3176
- const queryLen = m[2].length;
3177
- const start = off - prefixLen - queryLen;
3178
- if (start >= 0) {
3179
- const range = document.createRange();
3180
- range.setStart(node, start);
3181
- range.setEnd(node, off);
3182
- sel.removeAllRanges();
3183
- sel.addRange(range);
3184
- execCommand("delete");
3185
- }
3241
+ /** 弹层候选 → 删掉光标前的 @词、插入带 data-new 标记的芯片 + 尾随空格。 */
3242
+ const insertChipHtml = (0, react.useCallback)((html) => {
3243
+ const el = inputRef.current;
3244
+ const sel = window.getSelection();
3245
+ if (!el || !sel) return;
3246
+ if (!sel.anchorNode || !el.contains(sel.anchorNode)) return;
3247
+ el.focus({ preventScroll: true });
3248
+ const node = sel.anchorNode;
3249
+ if (node.nodeType === Node.TEXT_NODE) {
3250
+ const text = node.nodeValue || "";
3251
+ const off = sel.anchorOffset;
3252
+ const before = text.slice(0, off);
3253
+ const m = /(?:^|\s)@("?)([^"\s@]*)$/.exec(before);
3254
+ if (m) {
3255
+ const prefixLen = m[1] ? 2 : 1;
3256
+ const queryLen = m[2].length;
3257
+ const start = off - prefixLen - queryLen;
3258
+ if (start >= 0) {
3259
+ const range = document.createRange();
3260
+ range.setStart(node, start);
3261
+ range.setEnd(node, off);
3262
+ sel.removeAllRanges();
3263
+ sel.addRange(range);
3264
+ execCommand("delete");
3186
3265
  }
3187
3266
  }
3188
- execCommand("insertHTML", chipHtml(role).replace("class=\"dsgc-chipin\"", "class=\"dsgc-chipin\" data-new=\"\""));
3189
- const chip = el.querySelector(".dsgc-chipin[data-new]");
3190
- if (!chip) return;
3191
- const after = document.createRange();
3192
- after.setStartAfter(chip);
3193
- after.collapse(true);
3194
- sel.removeAllRanges();
3195
- sel.addRange(after);
3196
- execCommand("insertText", " ");
3197
- chip.removeAttribute("data-new");
3198
- setMention(null);
3199
- setMentionIdx(0);
3200
- syncFromDOM();
3201
- }, [
3202
- inputRef,
3203
- setMention,
3204
- setMentionIdx,
3205
- syncFromDOM
3206
- ]),
3267
+ }
3268
+ execCommand("insertHTML", html);
3269
+ const chip = el.querySelector(".dsgc-chipin[data-new]");
3270
+ if (!chip) return;
3271
+ const after = document.createRange();
3272
+ after.setStartAfter(chip);
3273
+ after.collapse(true);
3274
+ sel.removeAllRanges();
3275
+ sel.addRange(after);
3276
+ execCommand("insertText", " ");
3277
+ chip.removeAttribute("data-new");
3278
+ setMention(null);
3279
+ setMentionIdx(0);
3280
+ syncFromDOM();
3281
+ }, [
3282
+ inputRef,
3283
+ setMention,
3284
+ setMentionIdx,
3285
+ syncFromDOM
3286
+ ]);
3287
+ return {
3288
+ insertChip: (0, react.useCallback)((role) => {
3289
+ insertChipHtml(chipHtml(role, true));
3290
+ }, [insertChipHtml]),
3207
3291
  insertFileChip: (0, react.useCallback)((path, kind) => {
3208
- const el = inputRef.current;
3209
- const sel = window.getSelection();
3210
- if (!el || !sel) return;
3211
- if (!sel.anchorNode || !el.contains(sel.anchorNode)) return;
3212
- el.focus({ preventScroll: true });
3213
- const node = sel.anchorNode;
3214
- if (node.nodeType === Node.TEXT_NODE) {
3215
- const text = node.nodeValue || "";
3216
- const off = sel.anchorOffset;
3217
- const before = text.slice(0, off);
3218
- const m = /(?:^|\s)@("?)([^"\s@]*)$/.exec(before);
3219
- if (m) {
3220
- const prefixLen = m[1] ? 2 : 1;
3221
- const queryLen = m[2].length;
3222
- const start = off - prefixLen - queryLen;
3223
- if (start >= 0) {
3224
- const range = document.createRange();
3225
- range.setStart(node, start);
3226
- range.setEnd(node, off);
3227
- sel.removeAllRanges();
3228
- sel.addRange(range);
3229
- execCommand("delete");
3230
- }
3231
- }
3232
- }
3233
- execCommand("insertHTML", fileChipHtml(path, kind).replace("class=\"dsgc-chipin dsgc-chipin-file\"", "class=\"dsgc-chipin dsgc-chipin-file\" data-new=\"\""));
3234
- const chip = el.querySelector(".dsgc-chipin[data-new]");
3235
- if (!chip) return;
3236
- const after = document.createRange();
3237
- after.setStartAfter(chip);
3238
- after.collapse(true);
3239
- sel.removeAllRanges();
3240
- sel.addRange(after);
3241
- execCommand("insertText", " ");
3242
- chip.removeAttribute("data-new");
3243
- setMention(null);
3244
- setMentionIdx(0);
3245
- syncFromDOM();
3246
- }, [
3247
- inputRef,
3248
- setMention,
3249
- setMentionIdx,
3250
- syncFromDOM
3251
- ])
3292
+ insertChipHtml(fileChipHtml(path, kind, true));
3293
+ }, [insertChipHtml])
3252
3294
  };
3253
3295
  }
3254
3296
  function useInputKeyboard(mention, mentionCandidates, mentionIdxC, setMentionIdx, insertChip, insertFileChip, fileCandidates, setMention, sendMsg) {
@@ -3463,7 +3505,7 @@ window.__ModuleLoader__.load({
3463
3505
  * @module dsh-group-chat/client/panel
3464
3506
  */
3465
3507
  function GroupChatPanel() {
3466
- const { snap, gid, setGid, sid, setSid, search, setSearch, collapsedGroups, setCollapsedGroups, renameDraft, setRenameDraft, confirmDel, setConfirmDel, confirmClear, setConfirmClear, roleDraft, setRoleDraft, roleFormError, setRoleFormError, models, setModels, modelsError, setModelsError, fileBrowser, setFileBrowser, wsDraft, setWsDraft, partsSel, setPartsSel, rounds, setRounds, input, setInput, err, setErr, topicDraft, setTopicDraft, mention, setMention, mentionIdx, setMentionIdx, asideOpen, setAsideOpen, navOpen, setNavOpen, atBottom, setAtBottom, inputRef, sendingRef, scrollRef, action, mutate } = useGroupChatState();
3508
+ const { snap, gid, setGid, sid, setSid, search, setSearch, collapsedGroups, setCollapsedGroups, renameDraft, setRenameDraft, confirmDel, setConfirmDel, confirmClear, setConfirmClear, roleDraft, setRoleDraft, models, setModels, modelsError, setModelsError, partsSel, setPartsSel, rounds, setRounds, input, setInput, err, setErr, topicDraft, setTopicDraft, mention, setMention, mentionIdx, setMentionIdx, asideOpen, setAsideOpen, navOpen, setNavOpen, atBottom, setAtBottom, inputRef, sendingRef, scrollRef, action, mutate } = useGroupChatState();
3467
3509
  const [toast, setToast] = (0, react.useState)(null);
3468
3510
  const { onMsgsScroll } = useComposerEffects(inputRef, scrollRef, input, atBottom, snap);
3469
3511
  let group = snap && gid ? groupById(snap, gid) : null;
@@ -3486,13 +3528,14 @@ window.__ModuleLoader__.load({
3486
3528
  const { insertChip, insertFileChip } = useMentionChip(inputRef, setMention, setMentionIdx, syncFromDOM);
3487
3529
  const sendMsg = (0, react.useCallback)(async () => {
3488
3530
  if (busyNow || !sess || sendingRef.current) return;
3531
+ if (!input.trim()) return;
3489
3532
  sendingRef.current = true;
3490
3533
  try {
3491
- const parts = mentionedRoles.length ? mentionedRoles.map((r) => r.id) : participants;
3492
- if (!parts.length) {
3493
- setErr(mentionedRoles.length ? "" : "请至少选择一个参与角色(或在消息中 @成员)");
3494
- if (!mentionedRoles.length) return;
3534
+ if (!participants.length && !mentionedRoles.length) {
3535
+ setErr("请至少选择一个参与角色(或在消息中 @成员)");
3536
+ return;
3495
3537
  }
3538
+ const parts = mentionedRoles.length ? mentionedRoles.map((r) => r.id) : participants;
3496
3539
  const res = await action({
3497
3540
  kind: "send",
3498
3541
  sessionId: sess.id,
@@ -3607,80 +3650,15 @@ window.__ModuleLoader__.load({
3607
3650
  };
3608
3651
  const openRoleEditor = async (role) => {
3609
3652
  setErr("");
3610
- setRoleFormError("");
3611
3653
  if (!models || modelsError) await fetchModels();
3612
3654
  setRoleDraft(role ? draftFromRole(role) : blankDraft());
3613
3655
  };
3614
- const saveRole = async () => {
3615
- if (!roleDraft) return;
3616
- if (!roleDraft.name.trim()) {
3617
- setRoleFormError("角色名称不能为空");
3618
- return;
3619
- }
3620
- if (!roleDraft.provider || !roleDraft.model) {
3621
- setRoleFormError("请选择角色绑定的模型");
3622
- return;
3623
- }
3624
- const res = await mutate({
3625
- op: "upsertRole",
3626
- groupId: group.id,
3627
- role: roleDraft
3628
- });
3629
- if (res && res.ok && res.snapshot && !res.snapshot.error) {
3630
- setRoleDraft(null);
3631
- setRoleFormError("");
3632
- }
3633
- };
3634
3656
  const stopRun = async () => {
3635
3657
  await action({
3636
3658
  kind: "stop",
3637
3659
  sessionId: sess.id
3638
3660
  });
3639
3661
  };
3640
- const openBrowser = async (path) => {
3641
- setFileBrowser({
3642
- open: true,
3643
- loading: true,
3644
- list: null,
3645
- error: ""
3646
- });
3647
- try {
3648
- const res = await api.action({
3649
- kind: "browse",
3650
- path: path || ""
3651
- });
3652
- if (res && res.ok) setFileBrowser({
3653
- open: true,
3654
- loading: false,
3655
- list: res,
3656
- error: ""
3657
- });
3658
- else setFileBrowser({
3659
- open: true,
3660
- loading: false,
3661
- list: null,
3662
- error: res && res.error || "浏览失败"
3663
- });
3664
- } catch (e) {
3665
- setFileBrowser({
3666
- open: true,
3667
- loading: false,
3668
- list: null,
3669
- error: String(e && e.message || e)
3670
- });
3671
- }
3672
- };
3673
- const selectCurrentDir = () => {
3674
- if (!fileBrowser || !fileBrowser.list || !group) return;
3675
- const path = fileBrowser.list.path;
3676
- mutate({
3677
- op: "setWorkspaceDir",
3678
- groupId: group.id,
3679
- path
3680
- });
3681
- setWsDraft(null);
3682
- setFileBrowser(null);
3683
- };
3684
3662
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3685
3663
  className: "dsgc-root",
3686
3664
  children: [
@@ -3757,14 +3735,8 @@ window.__ModuleLoader__.load({
3757
3735
  snap,
3758
3736
  group,
3759
3737
  asideOpen,
3760
- wsDraft,
3761
- setWsDraft,
3762
- fileBrowser,
3763
- setFileBrowser,
3764
3738
  mutate,
3765
- openRoleEditor,
3766
- openBrowser,
3767
- selectCurrentDir
3739
+ openRoleEditor
3768
3740
  }),
3769
3741
  sess && confirmClear ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
3770
3742
  open: true,
@@ -3810,19 +3782,16 @@ window.__ModuleLoader__.load({
3810
3782
  roleDraft ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleDrawer, {
3811
3783
  draft: roleDraft,
3812
3784
  set: setRoleDraft,
3785
+ groupId: group.id,
3813
3786
  models,
3814
3787
  modelsError,
3815
3788
  onRetryModels: () => {
3816
3789
  fetchModels();
3817
3790
  },
3818
- onSave: () => {
3819
- saveRole();
3820
- },
3791
+ mutate,
3821
3792
  onCancel: () => {
3822
3793
  setRoleDraft(null);
3823
- setRoleFormError("");
3824
- },
3825
- formError: roleFormError
3794
+ }
3826
3795
  }) : null
3827
3796
  ]
3828
3797
  });