@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.
- package/README.md +93 -28
- package/lib/client.js +295 -326
- package/lib/client.js.map +1 -1
- package/lib/index.js +234 -221
- package/lib/types/client/components/AsidePanel.d.ts +3 -19
- package/lib/types/client/components/NavPanel.d.ts +9 -11
- package/lib/types/client/components/RoleDrawer.d.ts +3 -2
- package/lib/types/client/components/ToolRow.d.ts +2 -2
- package/lib/types/client/hooks/useGroupChatState.d.ts +0 -16
- package/lib/types/client/lib/model.d.ts +1 -7
- package/lib/types/client/utils/utils.d.ts +4 -4
- package/lib/types/core/json.d.ts +6 -0
- package/lib/types/core/types.d.ts +59 -64
- package/lib/types/host/engine/defaults.d.ts +13 -0
- package/lib/types/host/persistence/persistence.d.ts +2 -4
- package/lib/types/host/persistence/store.d.ts +5 -1
- package/lib/types/host/service.d.ts +2 -2
- package/lib/types/host/state.d.ts +1 -0
- package/lib/types/host/tools/tools.d.ts +2 -1
- package/lib/types/index.d.ts +3 -0
- package/lib/types/shared/file-mention-grammar.d.ts +3 -12
- package/package.json +4 -1
- package/src/client/GroupChatPanel.tsx +9 -56
- package/src/client/components/AsidePanel.tsx +40 -15
- package/src/client/components/ChatPanel.tsx +1 -1
- package/src/client/components/Composer.tsx +2 -2
- package/src/client/components/ConstraintList.tsx +1 -6
- package/src/client/components/MessageFlow.tsx +2 -1
- package/src/client/components/NavPanel.tsx +96 -78
- package/src/client/components/RoleDrawer.tsx +29 -7
- package/src/client/components/ToolRow.tsx +2 -2
- package/src/client/hooks/useComposer.ts +14 -53
- package/src/client/hooks/useGroupChatState.ts +2 -13
- package/src/client/lib/model.ts +1 -14
- package/src/client/lib/styles.ts +6 -2
- package/src/client/utils/utils.ts +6 -6
- package/src/core/constraints.ts +6 -12
- package/src/core/errors.ts +2 -12
- package/src/core/json.ts +17 -0
- package/src/core/types.ts +29 -20
- package/src/host/api/actions.ts +14 -13
- package/src/host/engine/conversation.ts +50 -51
- package/src/host/engine/defaults.ts +28 -0
- package/src/host/engine/fold.ts +2 -17
- package/src/host/engine/retitle.ts +9 -31
- package/src/host/materials/materials.ts +2 -0
- package/src/host/persistence/persistence.ts +57 -34
- package/src/host/persistence/store.ts +5 -1
- package/src/host/service.ts +4 -4
- package/src/host/state.ts +5 -2
- package/src/host/tools/tools.ts +42 -48
- package/src/index.ts +7 -5
- package/src/shared/file-mention-grammar.test.ts +1 -38
- package/src/shared/file-mention-grammar.ts +7 -33
- package/lib/types/client/Bubble.d.ts +0 -10
- package/lib/types/client/Glyph.d.ts +0 -12
- package/lib/types/client/GroupChatPanel.old.d.ts +0 -10
- package/lib/types/client/GroupChatSettingsSection.d.ts +0 -15
- package/lib/types/client/PermissionSelect.d.ts +0 -13
- package/lib/types/client/RoleDrawer.d.ts +0 -17
- package/lib/types/client/ThinkRow.d.ts +0 -9
- package/lib/types/client/ToolRow.d.ts +0 -9
- package/lib/types/client/api.d.ts +0 -8
- package/lib/types/client/components.d.ts +0 -22
- package/lib/types/client/drawer.d.ts +0 -17
- package/lib/types/client/glyph.d.ts +0 -12
- package/lib/types/client/model.d.ts +0 -78
- package/lib/types/client/panel.d.ts +0 -10
- package/lib/types/client/settings.d.ts +0 -15
- package/lib/types/client/styles.d.ts +0 -10
- package/lib/types/client/ui.d.ts +0 -17
- package/lib/types/client/utils.d.ts +0 -22
- package/lib/types/host/actions.d.ts +0 -34
- package/lib/types/host/conversation.d.ts +0 -25
- package/lib/types/host/http.d.ts +0 -16
- package/lib/types/host/materials.d.ts +0 -32
- package/lib/types/host/persistence.d.ts +0 -31
- package/lib/types/host/routes.d.ts +0 -11
- package/lib/types/host/store.d.ts +0 -65
- 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 (
|
|
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
|
-
|
|
752
|
+
formError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
733
753
|
className: "dsgc-formerr",
|
|
734
|
-
children:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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)(
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
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
|
-
|
|
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.
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)(
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
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
|
-
|
|
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.
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
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,
|
|
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 =
|
|
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,
|
|
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 ? "发送消息开始讨论;@成员
|
|
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
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
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
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
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
|
-
|
|
3209
|
-
|
|
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,
|
|
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
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
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
|
-
|
|
3819
|
-
saveRole();
|
|
3820
|
-
},
|
|
3791
|
+
mutate,
|
|
3821
3792
|
onCancel: () => {
|
|
3822
3793
|
setRoleDraft(null);
|
|
3823
|
-
|
|
3824
|
-
},
|
|
3825
|
-
formError: roleFormError
|
|
3794
|
+
}
|
|
3826
3795
|
}) : null
|
|
3827
3796
|
]
|
|
3828
3797
|
});
|