@roaming-ai/dsh-group-chat 0.2.2 → 0.3.1
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 +91 -27
- package/lib/client.js +1254 -598
- package/lib/client.js.map +1 -1
- package/lib/index.js +573 -109
- package/lib/types/client/components/AsidePanel.d.ts +3 -19
- package/lib/types/client/components/Bubble.d.ts +6 -3
- package/lib/types/client/components/ChatPanel.d.ts +1 -0
- package/lib/types/client/components/ConstraintList.d.ts +11 -0
- package/lib/types/client/components/FailCard.d.ts +9 -0
- package/lib/types/client/components/Fold.d.ts +19 -0
- package/lib/types/client/components/HoverTip.d.ts +22 -0
- package/lib/types/client/components/MessageFlow.d.ts +1 -0
- package/lib/types/client/components/MsgActions.d.ts +14 -0
- 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/useComposer.d.ts +6 -1
- package/lib/types/client/hooks/useGroupChatState.d.ts +0 -16
- package/lib/types/client/lib/composer-draft.d.ts +17 -0
- package/lib/types/client/lib/model.d.ts +1 -7
- package/lib/types/client/utils/utils.d.ts +4 -4
- package/lib/types/core/constraints.d.ts +61 -0
- package/lib/types/core/errors.d.ts +54 -0
- package/lib/types/core/json.d.ts +6 -0
- package/lib/types/core/types.d.ts +76 -60
- package/lib/types/host/api/actions.d.ts +1 -1
- package/lib/types/host/engine/conversation.d.ts +5 -3
- package/lib/types/host/engine/defaults.d.ts +13 -0
- package/lib/types/host/engine/fold.d.ts +17 -0
- package/lib/types/host/engine/index.d.ts +1 -0
- package/lib/types/host/engine/retitle.d.ts +5 -5
- package/lib/types/host/service.d.ts +1 -1
- package/lib/types/host/state.d.ts +2 -0
- package/lib/types/index.d.ts +2 -0
- package/lib/types/shared/file-mention-grammar.d.ts +3 -12
- package/package.json +1 -1
- package/src/client/GroupChatPanel.tsx +25 -59
- package/src/client/components/AsidePanel.tsx +50 -23
- package/src/client/components/Bubble.tsx +45 -18
- package/src/client/components/ChatPanel.tsx +17 -12
- package/src/client/components/Composer.tsx +26 -20
- package/src/client/components/ConstraintList.tsx +64 -0
- package/src/client/components/FailCard.tsx +49 -0
- package/src/client/components/Fold.tsx +72 -0
- package/src/client/components/HoverTip.tsx +134 -0
- package/src/client/components/MessageFlow.tsx +78 -54
- package/src/client/components/MsgActions.tsx +85 -0
- package/src/client/components/NavPanel.tsx +102 -79
- package/src/client/components/RoleDrawer.tsx +29 -7
- package/src/client/components/ThinkRow.tsx +7 -3
- package/src/client/components/ToolRow.tsx +9 -5
- package/src/client/hooks/useComposer.ts +54 -56
- package/src/client/hooks/useGroupChatState.ts +7 -17
- package/src/client/lib/composer-draft.ts +47 -0
- package/src/client/lib/model.ts +1 -14
- package/src/client/lib/styles.ts +73 -12
- package/src/client/react-dom-shim.d.ts +2 -0
- package/src/client/utils/utils.ts +6 -6
- package/src/core/constraints.ts +204 -0
- package/src/core/errors.ts +174 -0
- package/src/core/json.ts +18 -0
- package/src/core/types.ts +52 -19
- package/src/host/api/actions.ts +49 -14
- package/src/host/broadcast.ts +3 -3
- package/src/host/engine/conversation.ts +104 -40
- package/src/host/engine/defaults.ts +28 -0
- package/src/host/engine/fold.ts +99 -0
- package/src/host/engine/index.ts +1 -0
- package/src/host/engine/retitle.ts +24 -41
- package/src/host/materials/materials.ts +2 -0
- package/src/host/persistence/persistence.ts +18 -1
- package/src/host/service.ts +1 -1
- package/src/host/state.ts +5 -4
- package/src/host/tools/tools.ts +2 -2
- package/src/index.ts +2 -0
- 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
|
@@ -83,7 +83,32 @@ window.__ModuleLoader__.load({
|
|
|
83
83
|
".dsgc-seambtn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.15));color:var(--dsw-alias-label-primary,inherit);border-color:var(--dsw-alias-border-l3,rgba(128,128,128,.4))}",
|
|
84
84
|
".dsgc-seambtn.left{left:0;border-left:none;border-radius:0 8px 8px 0;box-shadow:3px 0 10px rgba(0,0,0,.07)}",
|
|
85
85
|
".dsgc-seambtn.right{right:0;border-right:none;border-radius:8px 0 0 8px;box-shadow:-3px 0 10px rgba(0,0,0,.07)}",
|
|
86
|
-
".dsgc-chathead{display:flex;
|
|
86
|
+
".dsgc-chathead{display:flex;flex-direction:column;gap:6px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,rgba(128,128,128,.2))}",
|
|
87
|
+
".dsgc-chathead-row{display:flex;gap:10px;align-items:center;min-width:0}",
|
|
88
|
+
".dsgc-constraints{align-self:center;box-sizing:border-box;width:76%;max-width:76%;display:flex;flex-direction:column;gap:8px;min-width:0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));background:var(--dsw-alias-bg-layer-3,rgba(128,128,128,.06));border-radius:12px;animation:dsgc-fade-in .18s ease-out}",
|
|
89
|
+
".dsgc-chead{display:flex;flex-direction:column;gap:2px;min-width:0}",
|
|
90
|
+
".dsgc-ctitle{margin:0;font:inherit;font-size:12px;font-weight:600;line-height:1.5;color:var(--dsw-alias-label-primary,inherit)}",
|
|
91
|
+
".dsgc-cdesc{margin:0;font-size:11.5px;line-height:1.5;color:var(--dsw-alias-label-tertiary,inherit)}",
|
|
92
|
+
".dsgc-fold{display:grid;grid-template-rows:0fr;transition:grid-template-rows .16s ease-in}",
|
|
93
|
+
".dsgc-fold.open{grid-template-rows:1fr;transition:grid-template-rows .22s cubic-bezier(.16,1,.3,1)}",
|
|
94
|
+
".dsgc-fold-inner{min-height:0;overflow:hidden;display:flex;flex-direction:column;gap:2px;opacity:0;transition:opacity .12s ease-in}",
|
|
95
|
+
".dsgc-fold.open .dsgc-fold-inner{opacity:1;transition:opacity .18s cubic-bezier(.16,1,.3,1)}",
|
|
96
|
+
".dsgc-clip{--dsgc-clip-from:var(--dsw-alias-bg-layer-3,rgba(128,128,128,.18));position:relative;min-width:0}",
|
|
97
|
+
".dsgc-clip::before,.dsgc-clip::after{content:\"\";position:absolute;left:0;right:0;height:14px;pointer-events:none;z-index:1;opacity:0;transition:opacity .12s}",
|
|
98
|
+
".dsgc-clip::before{top:0;background:linear-gradient(to bottom,var(--dsgc-clip-from),transparent)}",
|
|
99
|
+
".dsgc-clip::after{bottom:0;background:linear-gradient(to top,var(--dsgc-clip-from),transparent)}",
|
|
100
|
+
".dsgc-clip.can-up::before,.dsgc-clip.can-down::after{opacity:1}",
|
|
101
|
+
".dsgc-clip-scroll{min-width:0;overflow-y:auto;overscroll-behavior:contain;overflow-anchor:none;scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,rgba(128,128,128,.35)) transparent}",
|
|
102
|
+
".dsgc-clist{display:flex;flex-direction:column;gap:2px;min-width:0}",
|
|
103
|
+
".dsgc-constraint{display:flex;gap:8px;align-items:flex-start;min-width:0;font-size:11.5px;line-height:1.5}",
|
|
104
|
+
".dsgc-ckind{flex:none;font-weight:500}",
|
|
105
|
+
".dsgc-ckind.decided{color:var(--dsw-alias-label-secondary,inherit)}",
|
|
106
|
+
".dsgc-ckind.rejected{color:var(--dsw-alias-state-error-primary,#e5484d)}",
|
|
107
|
+
".dsgc-ckind.open{color:var(--dsw-alias-label-tertiary,inherit)}",
|
|
108
|
+
".dsgc-ctext{min-width:0;color:var(--dsw-alias-label-primary,inherit);overflow-wrap:break-word}",
|
|
109
|
+
".dsgc-cmore{align-self:flex-start;border:none;background:none;padding:0;font:inherit;font-size:11.5px;line-height:1.5;color:var(--dsw-alias-label-tertiary,inherit);cursor:pointer}",
|
|
110
|
+
".dsgc-cmore:hover{color:var(--dsw-alias-label-secondary,inherit)}",
|
|
111
|
+
".dsgc-cmore:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,var(--dsw-alias-state-business-primary,#4f6ef7));outline-offset:1px}",
|
|
87
112
|
".dsgc-sess-title{flex:none;font-weight:600;font-size:14px;color:var(--dsw-alias-label-primary,inherit);max-width:30%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
88
113
|
".dsgc-topic{flex:1;background:transparent;border:none;border-bottom:1px solid transparent;padding:2px;min-height:20px;color:var(--dsw-alias-label-secondary,inherit);font:inherit;outline:none;min-width:0;transition:color .12s,border-color .12s}",
|
|
89
114
|
".dsgc-topic:hover{border-bottom-color:var(--dsw-alias-border-l2,rgba(128,128,128,.3))}",
|
|
@@ -113,7 +138,8 @@ window.__ModuleLoader__.load({
|
|
|
113
138
|
".dsgc-think .dsgc-thinkrow{font-size:12px;color:var(--dsw-alias-label-secondary,inherit);border-radius:8px;padding:3px 8px;transition:background-color .12s}",
|
|
114
139
|
".dsgc-think .dsgc-thinkrow:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.1))}",
|
|
115
140
|
".dsgc-think .dsgc-thinksummary{color:var(--dsw-alias-label-tertiary,inherit);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:44ch;font-size:11.5px;margin-left:6px}",
|
|
116
|
-
".dsgc-think .dsgc-
|
|
141
|
+
".dsgc-think .dsgc-clip{margin:2px 0 4px;padding-left:10px;border-left:2px solid var(--dsw-alias-border-l2,rgba(128,128,128,.25))}",
|
|
142
|
+
".dsgc-think .dsgc-thinkbody{font-size:12.5px;line-height:1.7;color:var(--dsw-alias-label-secondary,inherit);white-space:pre-wrap;word-break:break-word}",
|
|
117
143
|
".dsgc-pending{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary,inherit)}",
|
|
118
144
|
".dsgc-pending svg{flex:none}",
|
|
119
145
|
".dsgc-pendingdots{display:inline-flex;gap:2px;align-items:center}",
|
|
@@ -122,16 +148,39 @@ window.__ModuleLoader__.load({
|
|
|
122
148
|
".dsgc-pendingdots i:nth-child(3){animation-delay:.4s}",
|
|
123
149
|
"@keyframes dsgc-dot-breathe{0%,100%{opacity:.25}50%{opacity:1}}",
|
|
124
150
|
".dsgc-sysmsg{align-self:center;font-size:11.5px;color:var(--dsw-alias-label-secondary,inherit);background:var(--dsw-alias-bg-module-platform,rgba(128,128,128,.1));border-radius:999px;padding:2px 10px;text-align:center;max-width:90%;animation:dsgc-sys-in .24s ease-out}",
|
|
151
|
+
".dsgc-msgops{display:inline-flex;align-items:center;gap:2px;margin-top:2px;min-height:24px;visibility:hidden;opacity:0;transition:opacity .12s,visibility .12s}",
|
|
152
|
+
".dsgc-msg:hover .dsgc-msgops,.dsgc-msg:focus-within .dsgc-msgops,.dsgc-msgops.always{visibility:visible;opacity:1}",
|
|
153
|
+
"@media (hover:none){.dsgc-msg .dsgc-msgops{visibility:visible;opacity:1}}",
|
|
154
|
+
".dsgc-msg.mine .dsgc-msgops{justify-content:flex-end}",
|
|
155
|
+
".dsgc-msgop{border:none;background:none;color:var(--dsw-alias-label-tertiary,inherit);cursor:pointer;padding:3px 6px;border-radius:6px;display:inline-flex;align-items:center;gap:4px;font:inherit;font-size:11.5px;line-height:16px;transition:background-color .12s,color .12s}",
|
|
156
|
+
".dsgc-msgop svg{flex:none}",
|
|
157
|
+
".dsgc-msgop:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.15));color:var(--dsw-alias-label-primary,inherit)}",
|
|
158
|
+
".dsgc-msgop:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,var(--dsw-alias-state-business-primary,#4f6ef7));outline-offset:1px}",
|
|
159
|
+
".dsgc-msgop.done{color:var(--dsw-alias-state-success-primary,#30a46c)}",
|
|
160
|
+
".dsgc-msgop.retry{color:var(--dsw-alias-state-error-primary,#e5484d)}",
|
|
161
|
+
".dsgc-msgop.retry:hover:not(:disabled){background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#e5484d) 10%,transparent);color:var(--dsw-alias-state-error-primary,#e5484d)}",
|
|
162
|
+
".dsgc-msgop:disabled{opacity:.4;cursor:default}",
|
|
163
|
+
".dsgc-fail{background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#e5484d) 8%,var(--dsw-alias-bg-layer-3,rgba(128,128,128,.06)));border:1px solid color-mix(in srgb,var(--dsw-alias-state-error-primary,#e5484d) 28%,var(--dsw-alias-border-l1,rgba(128,128,128,.15)));border-radius:12px;padding:10px 12px;display:flex;flex-direction:column;gap:6px;min-width:0;animation:dsgc-fade-in .18s ease-out}",
|
|
164
|
+
".dsgc-failhead{display:flex;align-items:flex-start;gap:8px;min-width:0}",
|
|
165
|
+
".dsgc-failicon{flex:none;width:22px;height:22px;border-radius:50%;display:inline-flex;align-items:center;justify-content:center;color:var(--dsw-alias-state-error-primary,#e5484d);background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#e5484d) 12%,transparent);margin-top:1px}",
|
|
166
|
+
".dsgc-failcopy{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}",
|
|
167
|
+
".dsgc-failtitle{font-size:13px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary,inherit)}",
|
|
168
|
+
".dsgc-faildetail{font-size:12px;line-height:1.5;color:var(--dsw-alias-label-secondary,inherit);overflow-wrap:anywhere}",
|
|
169
|
+
".dsgc-failmore{align-self:flex-start;border:none;background:none;padding:0;font:inherit;font-size:11.5px;line-height:1.5;color:var(--dsw-alias-label-tertiary,inherit);cursor:pointer}",
|
|
170
|
+
".dsgc-failmore:hover{color:var(--dsw-alias-label-secondary,inherit)}",
|
|
171
|
+
".dsgc-failmore:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,var(--dsw-alias-state-business-primary,#4f6ef7));outline-offset:1px}",
|
|
172
|
+
".dsgc-fail .dsgc-clip{--dsgc-clip-from:var(--dsw-alias-bg-layer-2,rgba(128,128,128,.12));border-radius:8px}",
|
|
173
|
+
".dsgc-failraw{margin:0;font-size:11.5px;line-height:1.55;white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-bg-layer-2,transparent);color:var(--dsw-alias-label-secondary,inherit)}",
|
|
125
174
|
".dsgc-tool{margin:2px 0}",
|
|
126
175
|
".dsgc-tool .dsgc-toolrow{font-size:12px;color:var(--dsw-alias-label-secondary,inherit);border-radius:8px;padding:3px 8px;transition:background-color .12s}",
|
|
127
176
|
".dsgc-tool .dsgc-toolrow:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.1))}",
|
|
128
177
|
".dsgc-tool .dsgc-toolsummary{color:var(--dsw-alias-label-tertiary,inherit);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:40ch;font-size:11.5px;margin-left:6px}",
|
|
129
|
-
".dsgc-tool .dsgc-
|
|
178
|
+
".dsgc-tool .dsgc-clip{margin:2px 0 4px;padding-left:10px;border-left:2px solid var(--dsw-alias-border-l2,rgba(128,128,128,.25))}",
|
|
179
|
+
".dsgc-tool .dsgc-toolbody{font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary,inherit);white-space:pre-wrap;word-break:break-word}",
|
|
130
180
|
".dsgc-confirm{border:1px solid var(--dsw-alias-state-business-primary,#4f6ef7);border-radius:12px;background:var(--dsw-alias-bg-layer-3,rgba(128,128,128,.06));padding:10px 12px;display:flex;flex-direction:column;gap:8px;max-width:76%;animation:dsgc-fade-in .18s ease-out}",
|
|
131
181
|
".dsgc-confirmtitle{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary,inherit);display:flex;align-items:center;gap:6px}",
|
|
132
182
|
".dsgc-confirmcmd{font-size:12px;line-height:1.6;white-space:pre-wrap;word-break:break-word;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));border-radius:8px;padding:8px 10px;background:var(--dsw-alias-bg-layer-2,transparent);color:var(--dsw-alias-label-primary,inherit);max-height:240px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,rgba(128,128,128,.35)) transparent}",
|
|
133
183
|
".dsgc-confirmops{display:flex;gap:8px;justify-content:flex-end}",
|
|
134
|
-
".dsgc-sysmsg.err{color:var(--dsw-alias-state-error-primary,#e5484d)}",
|
|
135
184
|
".dsgc-clearnotes{margin:0;padding-left:18px;font-size:13px;line-height:1.8;color:var(--dsw-alias-label-secondary,inherit);display:flex;flex-direction:column;gap:2px}",
|
|
136
185
|
".dsgc-err{font-size:12px;color:var(--dsw-alias-state-error-primary,#e5484d);animation:dsgc-fade-in .18s ease-out}",
|
|
137
186
|
".dsgc-empty{margin:auto;display:flex;flex-direction:column;align-items:center;gap:10px;color:var(--dsw-alias-label-tertiary,inherit);text-align:center;max-width:40ch;padding:24px;animation:dsgc-fade-in .3s ease-out}",
|
|
@@ -150,8 +199,9 @@ window.__ModuleLoader__.load({
|
|
|
150
199
|
".dsgc-card .dsgc-sendrow{padding:0 12px 10px}",
|
|
151
200
|
".dsgc-stopbtn{background:var(--dsw-alias-state-error-primary,#e5484d);border-color:transparent;color:#fff;font-weight:600}",
|
|
152
201
|
".dsgc-stopbtn:hover:not(:disabled){filter:brightness(1.08);color:#fff}",
|
|
153
|
-
".dsgc-parts{display:flex;
|
|
154
|
-
".dsgc-partslabel{font-size:12px;color:var(--dsw-alias-label-tertiary,inherit);flex:none}",
|
|
202
|
+
".dsgc-parts{display:flex;align-items:flex-start;gap:6px;min-height:24px}",
|
|
203
|
+
".dsgc-partslabel{font-size:12px;line-height:24px;color:var(--dsw-alias-label-tertiary,inherit);flex:none}",
|
|
204
|
+
".dsgc-partlist{display:flex;flex-wrap:wrap;align-items:center;gap:6px;row-gap:4px;flex:1;min-width:0}",
|
|
155
205
|
".dsgc-partchip{display:inline-flex;gap:6px;align-items:center;border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));background:transparent;color:var(--dsw-alias-label-secondary,inherit);border-radius:999px;padding:2px 10px;font-size:12px;line-height:1.5;cursor:pointer;transition:background-color .12s,color .12s,border-color .12s;font-family:inherit}",
|
|
156
206
|
".dsgc-partchip:hover{color:var(--dsw-alias-label-primary,inherit)}",
|
|
157
207
|
".dsgc-partchip.on{background:var(--dsw-alias-interactive-bg-active,rgba(128,128,128,.18));color:var(--dsw-alias-label-primary,inherit);border-color:var(--dsw-alias-border-l2,rgba(128,128,128,.3))}",
|
|
@@ -173,6 +223,11 @@ window.__ModuleLoader__.load({
|
|
|
173
223
|
".dsgc-rounds .dsgc-roundbtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.15));color:var(--dsw-alias-label-primary,inherit)}",
|
|
174
224
|
".dsgc-rounds .dsgc-roundbtn:disabled{opacity:.4;cursor:default}",
|
|
175
225
|
".dsgc-rounds .dsgc-roundnum{width:26px;text-align:center;font-variant-numeric:tabular-nums;font-weight:600;color:var(--dsw-alias-label-primary,inherit)}",
|
|
226
|
+
".dsgc-hovertip{position:fixed;z-index:100;width:max-content;max-width:50vw;padding:3px 7px;border-radius:8px;background:var(--dsw-alias-tooltip-bg,#1f1f1f);color:var(--dsw-static-neutral-bluish-00,#fff);font-size:13px;line-height:20px;white-space:pre-line;overflow-wrap:break-word;pointer-events:none;animation:dsgc-hovertip-in 150ms var(--ds-ease-in-out,ease)}",
|
|
227
|
+
".dsgc-hovertip[data-side=\"right\"]{transform:translateY(-50%)}",
|
|
228
|
+
".dsgc-hovertip[data-side=\"top\"]{transform:translate(-50%,-100%)}",
|
|
229
|
+
".dsgc-hovertip[data-side=\"bottom\"]{transform:translateX(-50%)}",
|
|
230
|
+
"@keyframes dsgc-hovertip-in{from{opacity:0}}",
|
|
176
231
|
".dsgc-mentionwrap{position:relative}",
|
|
177
232
|
".dsgc-mention{animation:dsgc-pop-in .16s cubic-bezier(.16,1,.3,1);position:absolute;left:0;right:0;bottom:calc(100% + 6px);background:var(--dsw-alias-bg-layer-3,#fff);border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));border-radius:10px;box-shadow:var(--dsw-shadow-lv3,0 8px 24px rgba(0,0,0,.18));max-height:220px;overflow-y:auto;padding:4px;z-index:30;display:flex;flex-direction:column;gap:2px;scrollbar-width:thin;scrollbar-color:var(--dsw-alias-scrollbar-bg-l2,rgba(128,128,128,.35)) transparent}",
|
|
178
233
|
".dsgc-mentionitem{display:flex;align-items:center;gap:8px;border:none;background:none;color:var(--dsw-alias-label-secondary,inherit);font:inherit;font-size:12.5px;text-align:left;cursor:pointer;padding:6px 8px;border-radius:6px;transition:background-color .12s,color .12s}",
|
|
@@ -206,10 +261,12 @@ window.__ModuleLoader__.load({
|
|
|
206
261
|
".dsgc-rolename{font-weight:600;font-size:13px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary,inherit)}",
|
|
207
262
|
".dsgc-role.off .dsgc-rolename{color:var(--dsw-alias-label-tertiary,inherit)}",
|
|
208
263
|
".dsgc-rolepersona{font-size:11.5px;color:var(--dsw-alias-label-tertiary,inherit);line-height:1.5;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:0}",
|
|
209
|
-
".dsgc-
|
|
210
|
-
".dsgc-
|
|
264
|
+
".dsgc-rolemeta{display:flex;align-items:center;gap:6px;min-width:0;flex:1}",
|
|
265
|
+
".dsgc-rolemodel{min-width:0;flex:0 1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--dsw-alias-bg-module-platform,rgba(128,128,128,.12));border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px;color:var(--dsw-alias-label-secondary,inherit)}",
|
|
266
|
+
".dsgc-rolethink{display:inline-flex;align-items:center;flex:none;color:var(--dsw-alias-label-tertiary,inherit)}",
|
|
267
|
+
".dsgc-roleops{display:flex;gap:2px;flex:none;margin-left:auto;opacity:0;transition:opacity .12s}",
|
|
211
268
|
".dsgc-role:hover .dsgc-roleops,.dsgc-role:focus-within .dsgc-roleops{opacity:1}",
|
|
212
|
-
".dsgc-rolemenu{display:flex;align-items:center;
|
|
269
|
+
".dsgc-rolemenu{display:flex;align-items:center;gap:8px}",
|
|
213
270
|
".dsgc-field{display:flex;flex-direction:column;gap:5px}",
|
|
214
271
|
".dsgc-field>label{font-size:12px;font-weight:500;color:var(--dsw-alias-label-secondary,inherit)}",
|
|
215
272
|
".dsgc-input,.dsgc-select,.dsgc-textarea{background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer-3,transparent));border:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));color:var(--dsw-alias-label-primary,inherit);font:inherit;font-size:13px;border-radius:8px;padding:6px 10px;width:100%;box-sizing:border-box;outline:none;transition:border-color .16s,background-color .16s}",
|
|
@@ -229,7 +286,6 @@ window.__ModuleLoader__.load({
|
|
|
229
286
|
"@keyframes dsgc-drawer-out{to{transform:translateX(32px);opacity:0}}",
|
|
230
287
|
"@keyframes dsgc-msg-in{from{opacity:0;transform:translateY(6px)}}",
|
|
231
288
|
"@keyframes dsgc-sys-in{from{opacity:0;transform:scale(.96)}}",
|
|
232
|
-
"@keyframes dsgc-think-in{from{opacity:0;transform:translateY(-3px)}}",
|
|
233
289
|
"@keyframes dsgc-pop-in{from{opacity:0;transform:translateY(4px)}}",
|
|
234
290
|
"@keyframes dsgc-rise-in{from{opacity:0;transform:translate(-50%,6px)}}",
|
|
235
291
|
"@keyframes dsgc-fade-in{from{opacity:0}}",
|
|
@@ -256,7 +312,7 @@ window.__ModuleLoader__.load({
|
|
|
256
312
|
".dsgc-fbsize{flex:none;font-size:10.5px;color:var(--dsw-alias-label-tertiary,inherit)}",
|
|
257
313
|
".dsgc-partchip:focus-visible,.dsgc-mentionitem:focus-visible,.dsgc-dot:focus-visible,.dsgc-opbtn:focus-visible,.dsgc-twist:focus-visible,.dsgc-addsess:focus-visible,.dsgc-grow-row:focus-visible,.dsgc-sess-row:focus-visible,.dsgc-rename:focus-visible,.dsgc-fbrow:focus-visible,.dsgc-role:focus-visible,.dsgc-roundbtn:focus-visible,.dsgc-tobtn:focus-visible,.dsgc-seambtn:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,var(--dsw-alias-state-business-primary,#4f6ef7));outline-offset:1px}",
|
|
258
314
|
"@container (max-width: 880px){.dsgc-aside{position:absolute;top:0;right:0;bottom:0;z-index:15;width:min(304px,88%);box-shadow:-12px 0 32px rgba(0,0,0,.16);border-left:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));transition:transform .3s cubic-bezier(.16,1,.3,1),opacity .2s ease}.dsgc-aside.closed{width:min(304px,88%);padding-left:12px;padding-right:12px;border-left-width:1px;transform:translateX(calc(100% + 14px));opacity:0;transition:transform .26s ease-in,opacity .18s ease-in,visibility 0s .24s}.dsgc-seambtn.right:not(.closed){right:min(304px,88%)}}",
|
|
259
|
-
"@container (max-width: 640px){.dsgc-nav{width:200px}.dsgc-msgbody{max-width:88%}}",
|
|
315
|
+
"@container (max-width: 640px){.dsgc-nav{width:200px}.dsgc-msgbody{max-width:88%}.dsgc-constraints{width:88%;max-width:88%}}",
|
|
260
316
|
"[class*=\"panelList\"]:has(.dsgc-entryOverlay){margin-top:4px}",
|
|
261
317
|
"[class*=\"panelRow\"]:has(.dsgc-entryOverlay){position:relative;box-sizing:border-box;height:36px;min-height:36px;padding:0 10px;font-size:13px;transition:background-color .12s,color .12s}",
|
|
262
318
|
"[class*=\"panelRow\"][class*=\"panelActive\"]:has(.dsgc-entryOverlay){font-weight:600}",
|
|
@@ -270,7 +326,7 @@ window.__ModuleLoader__.load({
|
|
|
270
326
|
".dsgc-entryIcon svg{width:18px;height:18px;display:block}",
|
|
271
327
|
"[data-sidebar-collapsed] .dsgc-entryOverlay,[class*=\"collapsed\"] .dsgc-entryOverlay{justify-content:center;padding:0}",
|
|
272
328
|
"[data-sidebar-collapsed] [class*=\"panelRow\"]:has(.dsgc-entryOverlay),[class*=\"collapsed\"] [class*=\"panelRow\"]:has(.dsgc-entryOverlay){margin:0 auto}",
|
|
273
|
-
"@media (prefers-reduced-motion:reduce){.dsgc-dot,.dsgc-partchip,.dsgc-mentionitem,.dsgc-grow-row,.dsgc-sess-row,.dsgc-opbtn,.dsgc-addsess,.dsgc-topic,.dsgc-role,.dsgc-rename,.dsgc-input,.dsgc-select,.dsgc-textarea,.dsgc-fbrow,.dsgc-twist svg,.dsgc-roleops,.dsgc-roundbtn,.dsgc-permtrigger,.dsgc-permchevron,.dsgc-tobtn,.dsgc-seambtn,.dsgc-aside,.dsgc-aside.closed,.dsgc-nav,.dsgc-nav.closed,.dsgc-clearbtn,[class*=\"panelRow\"]:has(.dsgc-entryOverlay){transition:none}.dsgc-dot:hover{transform:none}.dsgc-drawer,.dsgc-msg,.dsgc-sysmsg,.dsgc-mention,.dsgc-tobottom,.dsgc-err,.dsgc-empty,.dsgc-fb,.dsgc-
|
|
329
|
+
"@media (prefers-reduced-motion:reduce){.dsgc-dot,.dsgc-partchip,.dsgc-mentionitem,.dsgc-grow-row,.dsgc-sess-row,.dsgc-opbtn,.dsgc-addsess,.dsgc-topic,.dsgc-role,.dsgc-rename,.dsgc-input,.dsgc-select,.dsgc-textarea,.dsgc-fbrow,.dsgc-twist svg,.dsgc-roleops,.dsgc-roundbtn,.dsgc-permtrigger,.dsgc-permchevron,.dsgc-tobtn,.dsgc-seambtn,.dsgc-aside,.dsgc-aside.closed,.dsgc-nav,.dsgc-nav.closed,.dsgc-clearbtn,.dsgc-cmore,.dsgc-msgops,.dsgc-msgop,.dsgc-failmore,.dsgc-clip::before,.dsgc-clip::after,.dsgc-fold,.dsgc-fold-inner,.dsgc-fold.open,.dsgc-fold.open .dsgc-fold-inner,[class*=\"panelRow\"]:has(.dsgc-entryOverlay){transition:none}.dsgc-dot:hover{transform:none}.dsgc-drawer,.dsgc-msg,.dsgc-sysmsg,.dsgc-mention,.dsgc-tobottom,.dsgc-err,.dsgc-empty,.dsgc-fb,.dsgc-msg.live .dsgc-avatar,.dsgc-loading svg,.dsgc-pendingdots i,.dsgc-typing,.dsgc-constraints,.dsgc-hovertip,.dsgc-fail{animation:none}.dsgc-typing{background-position:0 0;background-size:100% 100%}}",
|
|
274
330
|
".dgcs-page{display:flex;flex-direction:column;gap:14px;padding:4px 0}",
|
|
275
331
|
".dgcs-head{display:flex;flex-direction:column;gap:6px}",
|
|
276
332
|
".dgcs-title{margin:0;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary,inherit)}",
|
|
@@ -436,14 +492,17 @@ window.__ModuleLoader__.load({
|
|
|
436
492
|
* @module dsh-group-chat/client/drawer
|
|
437
493
|
*/
|
|
438
494
|
function RoleDrawer(props) {
|
|
439
|
-
const { draft, set } = props;
|
|
495
|
+
const { draft, set, groupId } = props;
|
|
440
496
|
const models = props.models;
|
|
441
497
|
const providers = models && models.providers || [];
|
|
442
498
|
const modelsOf = models && models.modelsByProvider && models.modelsByProvider[draft.provider] || [];
|
|
443
499
|
const [closing, setClosing] = (0, react.useState)(false);
|
|
500
|
+
const closingRef = (0, react.useRef)(false);
|
|
501
|
+
const [formError, setFormError] = (0, react.useState)("");
|
|
444
502
|
const [effortsInfo, setEffortsInfo] = (0, react.useState)(null);
|
|
445
503
|
const close = () => {
|
|
446
|
-
if (
|
|
504
|
+
if (closingRef.current) return;
|
|
505
|
+
closingRef.current = true;
|
|
447
506
|
setClosing(true);
|
|
448
507
|
window.setTimeout(() => props.onCancel(), 140);
|
|
449
508
|
};
|
|
@@ -456,6 +515,23 @@ window.__ModuleLoader__.load({
|
|
|
456
515
|
reasoningEffort: "default"
|
|
457
516
|
}));
|
|
458
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
|
+
};
|
|
459
535
|
(0, react.useEffect)(() => {
|
|
460
536
|
const onKey = (e) => {
|
|
461
537
|
if (e.key === "Escape") close();
|
|
@@ -673,9 +749,9 @@ window.__ModuleLoader__.load({
|
|
|
673
749
|
children: "当前模型未提供推理等级,将使用默认"
|
|
674
750
|
})]
|
|
675
751
|
}) : null,
|
|
676
|
-
|
|
752
|
+
formError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
677
753
|
className: "dsgc-formerr",
|
|
678
|
-
children:
|
|
754
|
+
children: formError
|
|
679
755
|
}) : null
|
|
680
756
|
]
|
|
681
757
|
})
|
|
@@ -688,7 +764,9 @@ window.__ModuleLoader__.load({
|
|
|
688
764
|
children: "取消"
|
|
689
765
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
690
766
|
variant: "primary",
|
|
691
|
-
onClick:
|
|
767
|
+
onClick: () => {
|
|
768
|
+
save();
|
|
769
|
+
},
|
|
692
770
|
children: draft.id ? "保存" : "添加"
|
|
693
771
|
})]
|
|
694
772
|
})
|
|
@@ -696,7 +774,169 @@ window.__ModuleLoader__.load({
|
|
|
696
774
|
});
|
|
697
775
|
}
|
|
698
776
|
//#endregion
|
|
777
|
+
//#region src/client/components/HoverTip.tsx
|
|
778
|
+
/**
|
|
779
|
+
* 对齐宿主 Tooltip 的 hover 气泡:portal 到 document.body,躲开
|
|
780
|
+
* `.dsgc-root` 的 container-type 把 position:fixed 按容器定位。
|
|
781
|
+
* @module dsh-group-chat/client/components
|
|
782
|
+
*/
|
|
783
|
+
/**
|
|
784
|
+
* @param props.label 气泡正文(pre-line)
|
|
785
|
+
* @param props.side 默认 right(对齐侧栏会话行);composer 轮数用 top
|
|
786
|
+
* @param props.delayMs hover 延迟,默认 500;键盘 focus 立即出
|
|
787
|
+
*/
|
|
788
|
+
function HoverTip(props) {
|
|
789
|
+
const { label, side = "right", delayMs = 500, maxWidth, className, children } = props;
|
|
790
|
+
const anchorRef = (0, react.useRef)(null);
|
|
791
|
+
const bubbleRef = (0, react.useRef)(null);
|
|
792
|
+
const showTimer = (0, react.useRef)(null);
|
|
793
|
+
const hover = (0, react.useRef)(false);
|
|
794
|
+
const [pos, setPos] = (0, react.useState)(null);
|
|
795
|
+
const [placement, setPlacement] = (0, react.useState)(side);
|
|
796
|
+
const cancel = () => {
|
|
797
|
+
if (showTimer.current === null) return;
|
|
798
|
+
clearTimeout(showTimer.current);
|
|
799
|
+
showTimer.current = null;
|
|
800
|
+
};
|
|
801
|
+
const show = () => {
|
|
802
|
+
const el = anchorRef.current;
|
|
803
|
+
if (!el || !label) return;
|
|
804
|
+
const r = el.getBoundingClientRect();
|
|
805
|
+
setPlacement(side);
|
|
806
|
+
setPos({
|
|
807
|
+
x: side === "right" ? r.right + 10 : r.left + r.width / 2,
|
|
808
|
+
top: r.top,
|
|
809
|
+
bottom: r.bottom
|
|
810
|
+
});
|
|
811
|
+
};
|
|
812
|
+
const hideIfIdle = () => {
|
|
813
|
+
if (!hover.current && !(anchorRef.current && anchorRef.current.contains(document.activeElement))) setPos(null);
|
|
814
|
+
};
|
|
815
|
+
(0, react.useEffect)(() => cancel, []);
|
|
816
|
+
(0, react.useLayoutEffect)(() => {
|
|
817
|
+
if (!pos) return;
|
|
818
|
+
const fit = () => {
|
|
819
|
+
const el = bubbleRef.current;
|
|
820
|
+
if (!el) return;
|
|
821
|
+
el.style.left = pos.x + "px";
|
|
822
|
+
const r = el.getBoundingClientRect();
|
|
823
|
+
let dx = 0;
|
|
824
|
+
if (r.right > window.innerWidth - 12) dx = window.innerWidth - 12 - r.right;
|
|
825
|
+
if (r.left + dx < 12) dx = 12 - r.left;
|
|
826
|
+
el.style.left = pos.x + dx + "px";
|
|
827
|
+
if (side === "right") return;
|
|
828
|
+
const fitsBelow = pos.bottom + 8 + r.height <= window.innerHeight - 12;
|
|
829
|
+
const fitsAbove = pos.top - 8 - r.height >= 12;
|
|
830
|
+
if (placement === "bottom" && !fitsBelow && fitsAbove) setPlacement("top");
|
|
831
|
+
if (placement === "top" && !fitsAbove && fitsBelow) setPlacement("bottom");
|
|
832
|
+
};
|
|
833
|
+
fit();
|
|
834
|
+
window.addEventListener("resize", fit);
|
|
835
|
+
return () => {
|
|
836
|
+
window.removeEventListener("resize", fit);
|
|
837
|
+
};
|
|
838
|
+
}, [
|
|
839
|
+
pos,
|
|
840
|
+
placement,
|
|
841
|
+
label,
|
|
842
|
+
side
|
|
843
|
+
]);
|
|
844
|
+
const y = pos === null ? 0 : placement === "right" ? pos.top + (pos.bottom - pos.top) / 2 : placement === "top" ? pos.top - 8 : pos.bottom + 8;
|
|
845
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
846
|
+
ref: anchorRef,
|
|
847
|
+
className,
|
|
848
|
+
onMouseEnter: () => {
|
|
849
|
+
hover.current = true;
|
|
850
|
+
cancel();
|
|
851
|
+
if (delayMs <= 0) {
|
|
852
|
+
show();
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
showTimer.current = setTimeout(() => {
|
|
856
|
+
showTimer.current = null;
|
|
857
|
+
show();
|
|
858
|
+
}, delayMs);
|
|
859
|
+
},
|
|
860
|
+
onMouseLeave: () => {
|
|
861
|
+
hover.current = false;
|
|
862
|
+
cancel();
|
|
863
|
+
hideIfIdle();
|
|
864
|
+
},
|
|
865
|
+
onFocus: () => {
|
|
866
|
+
cancel();
|
|
867
|
+
show();
|
|
868
|
+
},
|
|
869
|
+
onBlur: (e) => {
|
|
870
|
+
const next = e.relatedTarget;
|
|
871
|
+
if (next && anchorRef.current && anchorRef.current.contains(next)) return;
|
|
872
|
+
cancel();
|
|
873
|
+
hideIfIdle();
|
|
874
|
+
},
|
|
875
|
+
children
|
|
876
|
+
}), pos ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
877
|
+
ref: bubbleRef,
|
|
878
|
+
className: "dsgc-hovertip",
|
|
879
|
+
"data-side": placement,
|
|
880
|
+
role: "tooltip",
|
|
881
|
+
style: {
|
|
882
|
+
left: pos.x,
|
|
883
|
+
top: y,
|
|
884
|
+
...maxWidth ? { maxWidth } : {}
|
|
885
|
+
},
|
|
886
|
+
children: label
|
|
887
|
+
}), document.body) : null] });
|
|
888
|
+
}
|
|
889
|
+
//#endregion
|
|
699
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
|
+
}
|
|
700
940
|
function NavPanel(props) {
|
|
701
941
|
const { snap, search, setSearch, collapsedGroups, setCollapsedGroups, gid, sid, setGid, setSid, setPartsSel, renameDraft, setRenameDraft, confirmDel, setConfirmDel, mutate, navOpen } = props;
|
|
702
942
|
const commitRename = async () => {
|
|
@@ -738,6 +978,20 @@ window.__ModuleLoader__.load({
|
|
|
738
978
|
sessionId: d.id
|
|
739
979
|
});
|
|
740
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
|
+
};
|
|
741
995
|
const q = search.trim().toLowerCase();
|
|
742
996
|
const groupMatches = (g) => {
|
|
743
997
|
if (!q) return {
|
|
@@ -754,6 +1008,7 @@ window.__ModuleLoader__.load({
|
|
|
754
1008
|
};
|
|
755
1009
|
};
|
|
756
1010
|
const group = gid ? groupById(snap, gid) : null;
|
|
1011
|
+
const selected = sid ? sessById(snap, sid) : null;
|
|
757
1012
|
const treeNodes = [];
|
|
758
1013
|
for (const g of snap.groups) {
|
|
759
1014
|
const match = groupMatches(g);
|
|
@@ -767,8 +1022,7 @@ window.__ModuleLoader__.load({
|
|
|
767
1022
|
const s = sessById(snap, sessionId);
|
|
768
1023
|
if (!s) continue;
|
|
769
1024
|
if (match.filterSessions && !s.name.toLowerCase().includes(q)) continue;
|
|
770
|
-
const
|
|
771
|
-
const isActive = sess && s.id === sess.id && g.id === group?.id;
|
|
1025
|
+
const isActive = selected && s.id === selected.id && g.id === group?.id;
|
|
772
1026
|
const isRenameSess = renameDraft && renameDraft.kind === "session" && renameDraft.id === s.id;
|
|
773
1027
|
const isConfirm = confirmDel && confirmDel.kind === "session" && confirmDel.id === s.id;
|
|
774
1028
|
const st = sessStatus(snap.run, s.id);
|
|
@@ -777,20 +1031,12 @@ window.__ModuleLoader__.load({
|
|
|
777
1031
|
role: "button",
|
|
778
1032
|
tabIndex: 0,
|
|
779
1033
|
onClick: () => {
|
|
780
|
-
|
|
781
|
-
setSid(s.id);
|
|
782
|
-
setPartsSel(null);
|
|
783
|
-
setConfirmDel(null);
|
|
784
|
-
setRenameDraft(null);
|
|
1034
|
+
pickSession(g.id, s.id);
|
|
785
1035
|
},
|
|
786
1036
|
onKeyDown: (e) => {
|
|
787
1037
|
if (e.key === "Enter" || e.key === " ") {
|
|
788
1038
|
e.preventDefault();
|
|
789
|
-
|
|
790
|
-
setSid(s.id);
|
|
791
|
-
setPartsSel(null);
|
|
792
|
-
setConfirmDel(null);
|
|
793
|
-
setRenameDraft(null);
|
|
1039
|
+
pickSession(g.id, s.id);
|
|
794
1040
|
}
|
|
795
1041
|
},
|
|
796
1042
|
children: [
|
|
@@ -806,57 +1052,39 @@ window.__ModuleLoader__.load({
|
|
|
806
1052
|
})
|
|
807
1053
|
}) : null
|
|
808
1054
|
}),
|
|
809
|
-
isRenameSess ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1055
|
+
isRenameSess ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenameField, {
|
|
1056
|
+
draft: renameDraft,
|
|
1057
|
+
set: setRenameDraft,
|
|
1058
|
+
commit: () => {
|
|
1059
|
+
commitRename();
|
|
1060
|
+
},
|
|
1061
|
+
keyDown: renameKeyDown
|
|
1062
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HoverTip, {
|
|
1063
|
+
label: s.name,
|
|
1064
|
+
side: "right",
|
|
1065
|
+
delayMs: 500,
|
|
1066
|
+
className: "dsgc-sess-name",
|
|
1067
|
+
children: s.name
|
|
1068
|
+
}),
|
|
1069
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NodeOps, {
|
|
1070
|
+
renameLabel: "重命名会话",
|
|
1071
|
+
deleteLabel: "删除会话",
|
|
1072
|
+
deleteTitle: isConfirm ? "再次点击确认删除" : "删除会话",
|
|
1073
|
+
danger: !!isConfirm,
|
|
1074
|
+
onRename: () => {
|
|
814
1075
|
setRenameDraft({
|
|
815
1076
|
kind: "session",
|
|
816
1077
|
id: s.id,
|
|
817
|
-
value:
|
|
1078
|
+
value: s.name
|
|
818
1079
|
});
|
|
819
1080
|
},
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1081
|
+
onDelete: () => {
|
|
1082
|
+
if (isConfirm) doDelete();
|
|
1083
|
+
else setConfirmDel({
|
|
1084
|
+
kind: "session",
|
|
1085
|
+
id: s.id
|
|
1086
|
+
});
|
|
826
1087
|
}
|
|
827
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
828
|
-
className: "dsgc-sess-name",
|
|
829
|
-
children: s.name
|
|
830
|
-
}),
|
|
831
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
832
|
-
className: "dsgc-nodeops",
|
|
833
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
834
|
-
className: "dsgc-opbtn",
|
|
835
|
-
title: "重命名会话",
|
|
836
|
-
"aria-label": "重命名会话",
|
|
837
|
-
onClick: (e) => {
|
|
838
|
-
e.stopPropagation();
|
|
839
|
-
setRenameDraft({
|
|
840
|
-
kind: "session",
|
|
841
|
-
id: s.id,
|
|
842
|
-
value: s.name
|
|
843
|
-
});
|
|
844
|
-
},
|
|
845
|
-
children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, 14)
|
|
846
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
847
|
-
className: "dsgc-opbtn" + (isConfirm ? " danger" : ""),
|
|
848
|
-
title: isConfirm ? "再次点击确认删除" : "删除会话",
|
|
849
|
-
"aria-label": "删除会话",
|
|
850
|
-
onClick: (e) => {
|
|
851
|
-
e.stopPropagation();
|
|
852
|
-
if (isConfirm) doDelete();
|
|
853
|
-
else setConfirmDel({
|
|
854
|
-
kind: "session",
|
|
855
|
-
id: s.id
|
|
856
|
-
});
|
|
857
|
-
},
|
|
858
|
-
children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, 14)
|
|
859
|
-
})]
|
|
860
1088
|
})
|
|
861
1089
|
]
|
|
862
1090
|
}, s.id));
|
|
@@ -879,18 +1107,12 @@ window.__ModuleLoader__.load({
|
|
|
879
1107
|
role: "button",
|
|
880
1108
|
tabIndex: 0,
|
|
881
1109
|
onClick: () => {
|
|
882
|
-
|
|
883
|
-
setPartsSel(null);
|
|
884
|
-
setConfirmDel(null);
|
|
885
|
-
setRenameDraft(null);
|
|
1110
|
+
pickGroup(g.id);
|
|
886
1111
|
},
|
|
887
1112
|
onKeyDown: (e) => {
|
|
888
1113
|
if (e.key === "Enter" || e.key === " ") {
|
|
889
1114
|
e.preventDefault();
|
|
890
|
-
|
|
891
|
-
setPartsSel(null);
|
|
892
|
-
setConfirmDel(null);
|
|
893
|
-
setRenameDraft(null);
|
|
1115
|
+
pickGroup(g.id);
|
|
894
1116
|
}
|
|
895
1117
|
},
|
|
896
1118
|
children: [
|
|
@@ -909,57 +1131,36 @@ window.__ModuleLoader__.load({
|
|
|
909
1131
|
},
|
|
910
1132
|
children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, 14)
|
|
911
1133
|
}),
|
|
912
|
-
isRenameGroup ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
onChange: (e) => {
|
|
917
|
-
setRenameDraft({
|
|
918
|
-
kind: "group",
|
|
919
|
-
id: g.id,
|
|
920
|
-
value: e.target.value
|
|
921
|
-
});
|
|
922
|
-
},
|
|
923
|
-
onBlur: () => {
|
|
1134
|
+
isRenameGroup ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenameField, {
|
|
1135
|
+
draft: renameDraft,
|
|
1136
|
+
set: setRenameDraft,
|
|
1137
|
+
commit: () => {
|
|
924
1138
|
commitRename();
|
|
925
1139
|
},
|
|
926
|
-
|
|
927
|
-
onClick: (e) => {
|
|
928
|
-
e.stopPropagation();
|
|
929
|
-
}
|
|
1140
|
+
keyDown: renameKeyDown
|
|
930
1141
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
931
1142
|
className: "dsgc-gname",
|
|
932
1143
|
children: g.name
|
|
933
1144
|
}),
|
|
934
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
onClick: (e) => {
|
|
954
|
-
e.stopPropagation();
|
|
955
|
-
if (isConfirmGroup) doDelete();
|
|
956
|
-
else setConfirmDel({
|
|
957
|
-
kind: "group",
|
|
958
|
-
id: g.id
|
|
959
|
-
});
|
|
960
|
-
},
|
|
961
|
-
children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, 14)
|
|
962
|
-
})]
|
|
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
|
+
}
|
|
963
1164
|
})
|
|
964
1165
|
]
|
|
965
1166
|
}), expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -998,8 +1199,14 @@ window.__ModuleLoader__.load({
|
|
|
998
1199
|
}
|
|
999
1200
|
//#endregion
|
|
1000
1201
|
//#region src/client/components/AsidePanel.tsx
|
|
1202
|
+
/**
|
|
1203
|
+
* 右侧成员与工作区栏组件。工作区目录编辑态(草稿、文件浏览器)为栏内自有状态。
|
|
1204
|
+
* @module dsh-group-chat/client/components
|
|
1205
|
+
*/
|
|
1001
1206
|
function AsidePanel(props) {
|
|
1002
|
-
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);
|
|
1003
1210
|
const commitWsDir = () => {
|
|
1004
1211
|
if (wsDraft !== null && group && wsDraft !== (group.workspaceDir || "")) mutate({
|
|
1005
1212
|
op: "setWorkspaceDir",
|
|
@@ -1008,6 +1215,50 @@ window.__ModuleLoader__.load({
|
|
|
1008
1215
|
});
|
|
1009
1216
|
setWsDraft(null);
|
|
1010
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
|
+
};
|
|
1011
1262
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1012
1263
|
className: "dsgc-aside" + (asideOpen ? "" : " closed"),
|
|
1013
1264
|
"aria-hidden": asideOpen ? void 0 : "true",
|
|
@@ -1101,8 +1352,9 @@ window.__ModuleLoader__.load({
|
|
|
1101
1352
|
}) : null,
|
|
1102
1353
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1103
1354
|
className: "dsgc-rolemenu",
|
|
1104
|
-
children: [
|
|
1105
|
-
|
|
1355
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1356
|
+
className: "dsgc-rolemeta",
|
|
1357
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1106
1358
|
className: "dsgc-rolemodel",
|
|
1107
1359
|
title: r.provider + " / " + r.model + (r.thinking ? " · 深度思考" + (r.reasoningEffort && r.reasoningEffort !== "default" ? "(" + r.reasoningEffort + ")" : "") : ""),
|
|
1108
1360
|
children: [
|
|
@@ -1110,42 +1362,36 @@ window.__ModuleLoader__.load({
|
|
|
1110
1362
|
" / ",
|
|
1111
1363
|
r.model
|
|
1112
1364
|
]
|
|
1113
|
-
}),
|
|
1114
|
-
|
|
1365
|
+
}), r.thinking ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1366
|
+
className: "dsgc-rolethink",
|
|
1115
1367
|
title: "深度思考",
|
|
1116
|
-
style: {
|
|
1117
|
-
display: "inline-flex",
|
|
1118
|
-
alignItems: "center",
|
|
1119
|
-
color: "var(--dsw-alias-label-tertiary,inherit)"
|
|
1120
|
-
},
|
|
1121
1368
|
children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconThinkOutline14, 14)
|
|
1122
|
-
}) : null
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
]
|
|
1369
|
+
}) : null]
|
|
1370
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1371
|
+
className: "dsgc-roleops",
|
|
1372
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1373
|
+
className: "dsgc-opbtn",
|
|
1374
|
+
title: "编辑角色",
|
|
1375
|
+
"aria-label": "编辑角色",
|
|
1376
|
+
onClick: (e) => {
|
|
1377
|
+
e.stopPropagation();
|
|
1378
|
+
openRoleEditor(r);
|
|
1379
|
+
},
|
|
1380
|
+
children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, 14)
|
|
1381
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1382
|
+
className: "dsgc-opbtn danger",
|
|
1383
|
+
title: "移除角色",
|
|
1384
|
+
"aria-label": "移除角色",
|
|
1385
|
+
onClick: (e) => {
|
|
1386
|
+
e.stopPropagation();
|
|
1387
|
+
mutate({
|
|
1388
|
+
op: "deleteRole",
|
|
1389
|
+
roleId: r.id
|
|
1390
|
+
});
|
|
1391
|
+
},
|
|
1392
|
+
children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, 14)
|
|
1393
|
+
})]
|
|
1394
|
+
})]
|
|
1149
1395
|
})
|
|
1150
1396
|
]
|
|
1151
1397
|
}, r.id);
|
|
@@ -1288,6 +1534,218 @@ window.__ModuleLoader__.load({
|
|
|
1288
1534
|
});
|
|
1289
1535
|
}
|
|
1290
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
|
|
1555
|
+
//#region src/core/types.ts
|
|
1556
|
+
/** 全部合法档位(展示顺序)。 */
|
|
1557
|
+
const PERMISSION_TIERS = [
|
|
1558
|
+
"view_only",
|
|
1559
|
+
"workspace_write",
|
|
1560
|
+
"full_access"
|
|
1561
|
+
];
|
|
1562
|
+
/** 档位安全化:合法字符串原样,其余 undefined。 */
|
|
1563
|
+
function asPermissionTier(value) {
|
|
1564
|
+
return typeof value === "string" && PERMISSION_TIERS.includes(value) ? value : void 0;
|
|
1565
|
+
}
|
|
1566
|
+
//#endregion
|
|
1567
|
+
//#region src/core/constraints.ts
|
|
1568
|
+
const KIND_LABEL = {
|
|
1569
|
+
decided: "已定",
|
|
1570
|
+
rejected: "否决",
|
|
1571
|
+
open: "未决"
|
|
1572
|
+
};
|
|
1573
|
+
//#endregion
|
|
1574
|
+
//#region src/core/errors.ts
|
|
1575
|
+
const PREFIX = "模型输出异常终止: ";
|
|
1576
|
+
const LEGACY_ROLE = /^角色「([^」]+)」发言失败[::]\s*/;
|
|
1577
|
+
/** 剥旧系统胶囊与引擎包装前缀,保留供应商原文。 */
|
|
1578
|
+
function unwrapSpeakFailure(raw) {
|
|
1579
|
+
const text = String(raw || "").trim();
|
|
1580
|
+
const legacy = parseLegacyRoleFailure(text);
|
|
1581
|
+
const body = legacy ? legacy.rest : text;
|
|
1582
|
+
return body.startsWith(PREFIX) ? body.slice(10).trim() : body;
|
|
1583
|
+
}
|
|
1584
|
+
/** 旧系统胶囊文案:角色「名」发言失败:原文。对不上则 null。 */
|
|
1585
|
+
function parseLegacyRoleFailure(text) {
|
|
1586
|
+
const m = LEGACY_ROLE.exec(String(text || ""));
|
|
1587
|
+
if (!m) return null;
|
|
1588
|
+
return {
|
|
1589
|
+
roleName: m[1],
|
|
1590
|
+
rest: String(text).slice(m[0].length)
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
/** 发言失败卡:error 标记,或旧系统胶囊文案。 */
|
|
1594
|
+
function isSpeakFailure(m) {
|
|
1595
|
+
return !!m.error || !!parseLegacyRoleFailure(m.text);
|
|
1596
|
+
}
|
|
1597
|
+
/** 失败回合对应角色:failedRoleId / speaker / 旧文案里的角色名(恰好一名才命中)。 */
|
|
1598
|
+
function resolveFailedRole(m, roles) {
|
|
1599
|
+
const byId = (id) => {
|
|
1600
|
+
if (!id) return null;
|
|
1601
|
+
for (const r of roles) if (r.id === id) return r;
|
|
1602
|
+
return null;
|
|
1603
|
+
};
|
|
1604
|
+
const direct = byId(m.failedRoleId) || (m.speaker !== "user" && m.speaker !== "system" ? byId(m.speaker) : null);
|
|
1605
|
+
if (direct) return direct;
|
|
1606
|
+
const parsed = parseLegacyRoleFailure(m.text);
|
|
1607
|
+
if (!parsed) return null;
|
|
1608
|
+
const hits = roles.filter((r) => r.name === parsed.roleName);
|
|
1609
|
+
return hits.length === 1 ? hits[0] : null;
|
|
1610
|
+
}
|
|
1611
|
+
function httpStatus(text) {
|
|
1612
|
+
const m = text.match(/\b([1-5]\d{2})\b/);
|
|
1613
|
+
if (!m) return null;
|
|
1614
|
+
const n = Number(m[1]);
|
|
1615
|
+
return n >= 100 && n <= 599 ? n : null;
|
|
1616
|
+
}
|
|
1617
|
+
function resetHint(message) {
|
|
1618
|
+
const m = message.match(/reset at ([^.]+\S)/i) || message.match(/将在\s*([^\s。]+)\s*重置/);
|
|
1619
|
+
return m ? "将在 " + m[1] + " 重置" : void 0;
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* 把供应商错误压成可扫描的标题 + 短因。
|
|
1623
|
+
* 未知形态回退为「发言失败」,原文仍可展开。
|
|
1624
|
+
*/
|
|
1625
|
+
function classifySpeakFailure(raw) {
|
|
1626
|
+
const source = unwrapSpeakFailure(raw);
|
|
1627
|
+
const blob = looseJson(source);
|
|
1628
|
+
const code = blob && typeof blob.code === "string" ? blob.code : "";
|
|
1629
|
+
const type = blob && typeof blob.type === "string" ? blob.type : "";
|
|
1630
|
+
const message = blob && typeof blob.message === "string" ? blob.message : source;
|
|
1631
|
+
const status = httpStatus(source);
|
|
1632
|
+
const joined = (code + " " + type + " " + message + " " + source).toLowerCase();
|
|
1633
|
+
if (/quota|accountquotaexceeded|exceeded the .*quota|额度|配额/.test(joined) || code === "AccountQuotaExceeded") return {
|
|
1634
|
+
title: "额度已用尽",
|
|
1635
|
+
detail: resetHint(message),
|
|
1636
|
+
raw: source
|
|
1637
|
+
};
|
|
1638
|
+
if (status === 429 || /too.?many.?requests|rate.?limit|限流/.test(joined) || type === "TooManyRequests") return {
|
|
1639
|
+
title: "请求过于频繁",
|
|
1640
|
+
detail: "稍后再试,或降低并发",
|
|
1641
|
+
raw: source
|
|
1642
|
+
};
|
|
1643
|
+
if (status === 401 || status === 403 || /unauthorized|forbidden|invalid.?api.?key|鉴权|未授权/.test(joined)) return {
|
|
1644
|
+
title: "模型鉴权失败",
|
|
1645
|
+
detail: "检查该角色绑定的提供方密钥",
|
|
1646
|
+
raw: source
|
|
1647
|
+
};
|
|
1648
|
+
if (status === 404 || /model.?not.?found|unknown.?model|模型不存在/.test(joined)) return {
|
|
1649
|
+
title: "模型不可用",
|
|
1650
|
+
detail: "该角色绑定的模型可能已下线",
|
|
1651
|
+
raw: source
|
|
1652
|
+
};
|
|
1653
|
+
if (/timeout|timed out|etimedout|超时/.test(joined)) return {
|
|
1654
|
+
title: "模型响应超时",
|
|
1655
|
+
raw: source
|
|
1656
|
+
};
|
|
1657
|
+
if (/network|econnreset|econnrefused|enotfound|fetch failed|网络/.test(joined)) return {
|
|
1658
|
+
title: "网络异常",
|
|
1659
|
+
detail: "检查网络后重试",
|
|
1660
|
+
raw: source
|
|
1661
|
+
};
|
|
1662
|
+
if (status !== null && status >= 500) return {
|
|
1663
|
+
title: "模型服务暂时不可用",
|
|
1664
|
+
detail: "HTTP " + status,
|
|
1665
|
+
raw: source
|
|
1666
|
+
};
|
|
1667
|
+
if (status !== null) return {
|
|
1668
|
+
title: "发言失败",
|
|
1669
|
+
detail: "HTTP " + status,
|
|
1670
|
+
raw: source
|
|
1671
|
+
};
|
|
1672
|
+
return {
|
|
1673
|
+
title: "发言失败",
|
|
1674
|
+
raw: source
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
/** 失败卡默认复制内容:标题 + 短因 + 原文。 */
|
|
1678
|
+
function formatSpeakFailureCopy(view) {
|
|
1679
|
+
const lines = [view.title];
|
|
1680
|
+
if (view.detail) lines.push(view.detail);
|
|
1681
|
+
if (view.raw && view.raw !== view.title) lines.push(view.raw);
|
|
1682
|
+
return lines.join("\n");
|
|
1683
|
+
}
|
|
1684
|
+
//#endregion
|
|
1685
|
+
//#region src/client/components/Fold.tsx
|
|
1686
|
+
/**
|
|
1687
|
+
* 会话内折叠:高度 0fr→1fr + 溢出滚动遮罩。思考 / 工具 / 失败原文 / 结论备忘共用。
|
|
1688
|
+
* @module dsh-group-chat/client/components
|
|
1689
|
+
*/
|
|
1690
|
+
function Fold(props) {
|
|
1691
|
+
const { open, children, className } = props;
|
|
1692
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1693
|
+
className: "dsgc-fold" + (open ? " open" : "") + (className ? " " + className : ""),
|
|
1694
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1695
|
+
className: "dsgc-fold-inner",
|
|
1696
|
+
children
|
|
1697
|
+
})
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
function ClipWell(props) {
|
|
1701
|
+
const { children, maxHeight, className, watch } = props;
|
|
1702
|
+
const [clip, setClip] = (0, react.useState)({
|
|
1703
|
+
up: false,
|
|
1704
|
+
down: false
|
|
1705
|
+
});
|
|
1706
|
+
const ref = (0, react.useRef)(null);
|
|
1707
|
+
(0, react.useLayoutEffect)(() => {
|
|
1708
|
+
const el = ref.current;
|
|
1709
|
+
if (!el) return;
|
|
1710
|
+
const measure = () => {
|
|
1711
|
+
if (el.clientHeight < 2) {
|
|
1712
|
+
setClip((cur) => cur.up || cur.down ? {
|
|
1713
|
+
up: false,
|
|
1714
|
+
down: false
|
|
1715
|
+
} : cur);
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
const max = el.scrollHeight - el.clientHeight;
|
|
1719
|
+
const up = el.scrollTop > 1;
|
|
1720
|
+
const down = max > 1 && el.scrollTop < max - 1;
|
|
1721
|
+
setClip((cur) => cur.up === up && cur.down === down ? cur : {
|
|
1722
|
+
up,
|
|
1723
|
+
down
|
|
1724
|
+
});
|
|
1725
|
+
};
|
|
1726
|
+
measure();
|
|
1727
|
+
const ro = new ResizeObserver(measure);
|
|
1728
|
+
ro.observe(el);
|
|
1729
|
+
for (const child of el.children) ro.observe(child);
|
|
1730
|
+
el.addEventListener("scroll", measure, { passive: true });
|
|
1731
|
+
el.addEventListener("transitionend", measure);
|
|
1732
|
+
return () => {
|
|
1733
|
+
ro.disconnect();
|
|
1734
|
+
el.removeEventListener("scroll", measure);
|
|
1735
|
+
el.removeEventListener("transitionend", measure);
|
|
1736
|
+
};
|
|
1737
|
+
}, [watch]);
|
|
1738
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1739
|
+
className: "dsgc-clip" + (clip.up ? " can-up" : "") + (clip.down ? " can-down" : ""),
|
|
1740
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1741
|
+
className: "dsgc-clip-scroll" + (className ? " " + className : ""),
|
|
1742
|
+
ref,
|
|
1743
|
+
style: { maxHeight },
|
|
1744
|
+
children
|
|
1745
|
+
})
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
//#endregion
|
|
1291
1749
|
//#region src/client/components/ThinkRow.tsx
|
|
1292
1750
|
/**
|
|
1293
1751
|
* 思考折叠行(对标宿主 ReasoningRow:折叠摘要 / 展开全文)。
|
|
@@ -1299,9 +1757,9 @@ window.__ModuleLoader__.load({
|
|
|
1299
1757
|
const [expanded, setExpanded] = (0, react.useState)(false);
|
|
1300
1758
|
const plain = (line) => line.replace(/^(\s*#{1,6}\s+|\s*[-*+]\s+|\s*>\s*)+/, "").replace(/[*`_~]/g, "").replace(/\s+/g, " ").trim();
|
|
1301
1759
|
const summary = plain(running ? latestLine(text) : firstLine(text));
|
|
1302
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1760
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1303
1761
|
className: "dsgc-think",
|
|
1304
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
|
|
1762
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
|
|
1305
1763
|
rowClassName: "dsgc-thinkrow",
|
|
1306
1764
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconThinkOutline14, { size: 14 }),
|
|
1307
1765
|
title: "思考",
|
|
@@ -1314,12 +1772,18 @@ window.__ModuleLoader__.load({
|
|
|
1314
1772
|
collapsedContent: text ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1315
1773
|
className: "dsgc-thinksummary",
|
|
1316
1774
|
children: summary
|
|
1317
|
-
}) : null
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1775
|
+
}) : null
|
|
1776
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Fold, {
|
|
1777
|
+
open: expanded,
|
|
1778
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClipWell, {
|
|
1779
|
+
maxHeight: 320,
|
|
1780
|
+
watch: text,
|
|
1781
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1782
|
+
className: "dsgc-thinkbody",
|
|
1783
|
+
children: text
|
|
1784
|
+
})
|
|
1321
1785
|
})
|
|
1322
|
-
})
|
|
1786
|
+
})]
|
|
1323
1787
|
});
|
|
1324
1788
|
}
|
|
1325
1789
|
//#endregion
|
|
@@ -1347,9 +1811,9 @@ window.__ModuleLoader__.load({
|
|
|
1347
1811
|
const statusText = c.status === "ok" ? "成功" : c.status === "denied" ? "用户拒绝" : "失败";
|
|
1348
1812
|
const StatusIcon = c.status === "ok" ? _deepseek_ai_dsh_client_ui_primitives.IconCheckOutline14 : c.status === "denied" ? _deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16 : _deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16;
|
|
1349
1813
|
const dur = c.durationMs ? c.durationMs >= 1e3 ? (c.durationMs / 1e3).toFixed(1) + "s" : c.durationMs + "ms" : "";
|
|
1350
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1814
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1351
1815
|
className: "dsgc-tool",
|
|
1352
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
|
|
1816
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.DisclosureRow, {
|
|
1353
1817
|
rowClassName: "dsgc-toolrow",
|
|
1354
1818
|
icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconOf, { size: 14 }),
|
|
1355
1819
|
title: c.tool + (brief ? " " + brief : ""),
|
|
@@ -1366,12 +1830,138 @@ window.__ModuleLoader__.load({
|
|
|
1366
1830
|
marginRight: 4,
|
|
1367
1831
|
color: c.status === "ok" ? "var(--dsw-alias-label-tertiary,inherit)" : "var(--dsw-alias-state-error-primary,#e5484d)"
|
|
1368
1832
|
}), statusText + (dur ? " · " + dur : "")]
|
|
1369
|
-
}),
|
|
1370
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1371
|
-
className: "dsgc-toolbody",
|
|
1372
|
-
children: c.output || "(无输出)"
|
|
1373
1833
|
})
|
|
1374
|
-
})
|
|
1834
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Fold, {
|
|
1835
|
+
open: expanded,
|
|
1836
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClipWell, {
|
|
1837
|
+
maxHeight: 260,
|
|
1838
|
+
watch: c.output,
|
|
1839
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1840
|
+
className: "dsgc-toolbody",
|
|
1841
|
+
children: c.output || "(无输出)"
|
|
1842
|
+
})
|
|
1843
|
+
})
|
|
1844
|
+
})]
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
//#endregion
|
|
1848
|
+
//#region src/client/components/FailCard.tsx
|
|
1849
|
+
/**
|
|
1850
|
+
* 角色发言失败卡:人话标题 + 可展开原文。操作条由 Bubble 放在气泡外下方。
|
|
1851
|
+
* @module dsh-group-chat/client/FailCard
|
|
1852
|
+
*/
|
|
1853
|
+
function FailCard(props) {
|
|
1854
|
+
const view = classifySpeakFailure(props.raw);
|
|
1855
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1856
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1857
|
+
className: "dsgc-fail",
|
|
1858
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1859
|
+
className: "dsgc-failhead",
|
|
1860
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1861
|
+
className: "dsgc-failicon",
|
|
1862
|
+
"aria-hidden": "true",
|
|
1863
|
+
children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, 14)
|
|
1864
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1865
|
+
className: "dsgc-failcopy",
|
|
1866
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1867
|
+
className: "dsgc-failtitle",
|
|
1868
|
+
children: view.title
|
|
1869
|
+
}), view.detail ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1870
|
+
className: "dsgc-faildetail",
|
|
1871
|
+
children: view.detail
|
|
1872
|
+
}) : null]
|
|
1873
|
+
})]
|
|
1874
|
+
}), view.raw ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1875
|
+
type: "button",
|
|
1876
|
+
className: "dsgc-failmore" + (open ? " open" : ""),
|
|
1877
|
+
"aria-expanded": open,
|
|
1878
|
+
onClick: () => {
|
|
1879
|
+
setOpen((v) => !v);
|
|
1880
|
+
},
|
|
1881
|
+
children: open ? "收起原始错误" : "查看原始错误"
|
|
1882
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Fold, {
|
|
1883
|
+
open,
|
|
1884
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ClipWell, {
|
|
1885
|
+
maxHeight: 220,
|
|
1886
|
+
watch: open,
|
|
1887
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
1888
|
+
className: "dsgc-failraw",
|
|
1889
|
+
children: view.raw
|
|
1890
|
+
})
|
|
1891
|
+
})
|
|
1892
|
+
})] }) : null]
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
//#endregion
|
|
1896
|
+
//#region src/client/components/MsgActions.tsx
|
|
1897
|
+
/**
|
|
1898
|
+
* 消息操作条:贴在气泡外下方。用户/角色消息悬停出「复制」;失败卡常驻「复制 / 重试」。
|
|
1899
|
+
* @module dsh-group-chat/client/MsgActions
|
|
1900
|
+
*/
|
|
1901
|
+
async function writeClipboard(text) {
|
|
1902
|
+
try {
|
|
1903
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
1904
|
+
await navigator.clipboard.writeText(text);
|
|
1905
|
+
return true;
|
|
1906
|
+
}
|
|
1907
|
+
} catch {}
|
|
1908
|
+
try {
|
|
1909
|
+
const el = document.createElement("textarea");
|
|
1910
|
+
el.value = text;
|
|
1911
|
+
el.setAttribute("readonly", "");
|
|
1912
|
+
el.style.position = "fixed";
|
|
1913
|
+
el.style.left = "-9999px";
|
|
1914
|
+
document.body.appendChild(el);
|
|
1915
|
+
el.select();
|
|
1916
|
+
const ok = document.execCommand("copy");
|
|
1917
|
+
document.body.removeChild(el);
|
|
1918
|
+
return ok;
|
|
1919
|
+
} catch {
|
|
1920
|
+
return false;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
function MsgActions(props) {
|
|
1924
|
+
const { copyText, onRetry, retryDisabled, retryTitle, always } = props;
|
|
1925
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
1926
|
+
const copiedTimer = (0, react.useRef)(null);
|
|
1927
|
+
(0, react.useEffect)(() => () => {
|
|
1928
|
+
if (copiedTimer.current) clearTimeout(copiedTimer.current);
|
|
1929
|
+
}, []);
|
|
1930
|
+
const copy = async () => {
|
|
1931
|
+
if (!copyText) return;
|
|
1932
|
+
if (!await writeClipboard(copyText)) return;
|
|
1933
|
+
setCopied(true);
|
|
1934
|
+
if (copiedTimer.current) clearTimeout(copiedTimer.current);
|
|
1935
|
+
copiedTimer.current = setTimeout(() => {
|
|
1936
|
+
setCopied(false);
|
|
1937
|
+
}, 1400);
|
|
1938
|
+
};
|
|
1939
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1940
|
+
className: "dsgc-msgops" + (always ? " always" : ""),
|
|
1941
|
+
role: "group",
|
|
1942
|
+
"aria-label": "消息操作",
|
|
1943
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1944
|
+
type: "button",
|
|
1945
|
+
className: "dsgc-msgop" + (copied ? " done" : ""),
|
|
1946
|
+
title: copied ? "已复制" : "复制",
|
|
1947
|
+
"aria-label": copied ? "已复制" : "复制",
|
|
1948
|
+
onClick: (e) => {
|
|
1949
|
+
e.stopPropagation();
|
|
1950
|
+
copy();
|
|
1951
|
+
},
|
|
1952
|
+
children: [Icon(copied ? _deepseek_ai_dsh_client_ui_primitives.IconCheckOutline14 : _deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, 14), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: copied ? "已复制" : "复制" })]
|
|
1953
|
+
}), onRetry ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1954
|
+
type: "button",
|
|
1955
|
+
className: "dsgc-msgop retry",
|
|
1956
|
+
title: retryTitle || "重试",
|
|
1957
|
+
"aria-label": retryTitle || "重试",
|
|
1958
|
+
disabled: retryDisabled,
|
|
1959
|
+
onClick: (e) => {
|
|
1960
|
+
e.stopPropagation();
|
|
1961
|
+
if (!retryDisabled) onRetry();
|
|
1962
|
+
},
|
|
1963
|
+
children: [Icon(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline14, 14), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "重试" })]
|
|
1964
|
+
}) : null]
|
|
1375
1965
|
});
|
|
1376
1966
|
}
|
|
1377
1967
|
//#endregion
|
|
@@ -1385,62 +1975,78 @@ window.__ModuleLoader__.load({
|
|
|
1385
1975
|
* MarkdownText 重新解析打满主线程导致「卡死后一次性蹦出」。
|
|
1386
1976
|
* @module dsh-group-chat/client/Bubble
|
|
1387
1977
|
*/
|
|
1388
|
-
function BubbleInner({ m, role }) {
|
|
1978
|
+
function BubbleInner({ m, role, busy, onRetry }) {
|
|
1389
1979
|
const isUser = m.speaker === "user";
|
|
1390
|
-
const
|
|
1391
|
-
const
|
|
1980
|
+
const isFail = isSpeakFailure(m);
|
|
1981
|
+
const isSys = m.speaker === "system" && !isFail && !role;
|
|
1982
|
+
const name = isUser ? "我" : role ? role.name : isSys ? "系统" : "成员";
|
|
1392
1983
|
if (isSys) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1393
|
-
className: "dsgc-sysmsg"
|
|
1984
|
+
className: "dsgc-sysmsg",
|
|
1394
1985
|
children: m.text
|
|
1395
1986
|
});
|
|
1987
|
+
const retryTitle = !role ? "失败角色已不存在,无法重试" : !role.enabled ? "该角色已停用,无法重试" : busy ? "已有对话进行中,请先停止" : "重试该角色发言";
|
|
1988
|
+
const copyText = isFail ? formatSpeakFailureCopy(classifySpeakFailure(m.text)) : m.text || "";
|
|
1396
1989
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1397
|
-
className: "dsgc-msg" + (isUser ? " mine" : ""),
|
|
1990
|
+
className: "dsgc-msg" + (isUser ? " mine" : "") + (isFail ? " fail" : ""),
|
|
1398
1991
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1399
1992
|
className: "dsgc-avatar" + (isUser ? " mine" : ""),
|
|
1400
1993
|
style: isUser || !role ? void 0 : { border: "2px solid " + (role.color || "#888") },
|
|
1401
1994
|
children: name.slice(0, 1)
|
|
1402
1995
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1403
1996
|
className: "dsgc-msgbody",
|
|
1404
|
-
children: [
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1997
|
+
children: [
|
|
1998
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1999
|
+
className: "dsgc-msghead",
|
|
2000
|
+
children: [
|
|
2001
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2002
|
+
className: "dsgc-msgname",
|
|
2003
|
+
children: name
|
|
2004
|
+
}),
|
|
2005
|
+
m.model ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2006
|
+
className: "dsgc-msgmodel",
|
|
2007
|
+
title: m.model,
|
|
2008
|
+
children: m.model
|
|
2009
|
+
}) : null,
|
|
2010
|
+
m.ts ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2011
|
+
className: "dsgc-msgtime",
|
|
2012
|
+
children: fmtTime(m.ts)
|
|
2013
|
+
}) : null
|
|
2014
|
+
]
|
|
2015
|
+
}),
|
|
2016
|
+
isFail ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailCard, { raw: m.text }) : isUser ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2017
|
+
className: "dsgc-msgtext",
|
|
2018
|
+
children: m.text
|
|
2019
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2020
|
+
className: "dsgc-msgtext",
|
|
2021
|
+
children: [
|
|
2022
|
+
m.reasoning ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThinkRow, { text: m.reasoning }) : null,
|
|
2023
|
+
(Array.isArray(m.toolCalls) ? m.toolCalls : []).map((c, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolRow, { c }, "tc" + i)),
|
|
2024
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
|
|
2025
|
+
text: m.text || "(无内容)",
|
|
2026
|
+
labels: MD_LABELS
|
|
2027
|
+
})
|
|
2028
|
+
]
|
|
2029
|
+
}),
|
|
2030
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MsgActions, {
|
|
2031
|
+
copyText,
|
|
2032
|
+
always: isFail,
|
|
2033
|
+
onRetry: isFail && onRetry ? () => {
|
|
2034
|
+
onRetry(m.id);
|
|
2035
|
+
} : void 0,
|
|
2036
|
+
retryDisabled: busy || !role || !role.enabled,
|
|
2037
|
+
retryTitle
|
|
2038
|
+
})
|
|
2039
|
+
]
|
|
1435
2040
|
})]
|
|
1436
2041
|
});
|
|
1437
2042
|
}
|
|
1438
2043
|
/** 渲染相关字段的值比较(消息不可变;角色仅名/色参与渲染)。 */
|
|
1439
2044
|
function bubblePropsEqual(a, b) {
|
|
2045
|
+
if (a.busy !== b.busy || a.onRetry == null !== (b.onRetry == null)) return false;
|
|
1440
2046
|
const x = a.m;
|
|
1441
2047
|
const y = b.m;
|
|
1442
2048
|
if (x !== y) {
|
|
1443
|
-
if (x.id !== y.id || x.speaker !== y.speaker || x.text !== y.text || x.reasoning !== y.reasoning || x.model !== y.model || x.error !== y.error || x.ts !== y.ts) return false;
|
|
2049
|
+
if (x.id !== y.id || x.speaker !== y.speaker || x.text !== y.text || x.reasoning !== y.reasoning || x.model !== y.model || x.error !== y.error || x.failedRoleId !== y.failedRoleId || x.ts !== y.ts) return false;
|
|
1444
2050
|
const ta = Array.isArray(x.toolCalls) ? x.toolCalls : [];
|
|
1445
2051
|
const tb = Array.isArray(y.toolCalls) ? y.toolCalls : [];
|
|
1446
2052
|
if (ta.length !== tb.length) return false;
|
|
@@ -1452,151 +2058,219 @@ window.__ModuleLoader__.load({
|
|
|
1452
2058
|
}
|
|
1453
2059
|
const ra = a.role;
|
|
1454
2060
|
const rb = b.role;
|
|
1455
|
-
return (ra ? ra.name + "\0" + (ra.color || "") : "") === (rb ? rb.name + "\0" + (rb.color || "") : "");
|
|
2061
|
+
return (ra ? ra.id + "\0" + ra.name + "\0" + (ra.color || "") + "\0" + (ra.enabled ? "1" : "0") : "") === (rb ? rb.id + "\0" + rb.name + "\0" + (rb.color || "") + "\0" + (rb.enabled ? "1" : "0") : "");
|
|
1456
2062
|
}
|
|
1457
2063
|
const Bubble = (0, react.memo)(BubbleInner, bubblePropsEqual);
|
|
1458
2064
|
//#endregion
|
|
2065
|
+
//#region src/client/components/ConstraintList.tsx
|
|
2066
|
+
/**
|
|
2067
|
+
* 会话流折点处:只读结论备忘卡(超过 4 条默认露 3 条)。
|
|
2068
|
+
* @module dsh-group-chat/client/components
|
|
2069
|
+
*/
|
|
2070
|
+
function ConstraintRow(props) {
|
|
2071
|
+
const { item } = props;
|
|
2072
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2073
|
+
className: "dsgc-constraint",
|
|
2074
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2075
|
+
className: "dsgc-ckind " + item.kind,
|
|
2076
|
+
children: KIND_LABEL[item.kind]
|
|
2077
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2078
|
+
className: "dsgc-ctext",
|
|
2079
|
+
children: item.text
|
|
2080
|
+
})]
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
2083
|
+
function ConstraintList(props) {
|
|
2084
|
+
const { items } = props;
|
|
2085
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
2086
|
+
if (!items.length) return null;
|
|
2087
|
+
const overflow = items.length > 4;
|
|
2088
|
+
const head = overflow ? items.slice(0, 3) : items;
|
|
2089
|
+
const extra = overflow ? items.slice(3) : [];
|
|
2090
|
+
const rest = items.length - 3;
|
|
2091
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2092
|
+
className: "dsgc-constraints",
|
|
2093
|
+
"aria-labelledby": "dsgc-constraints-title",
|
|
2094
|
+
children: [
|
|
2095
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2096
|
+
className: "dsgc-chead",
|
|
2097
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
2098
|
+
id: "dsgc-constraints-title",
|
|
2099
|
+
className: "dsgc-ctitle",
|
|
2100
|
+
children: "结论备忘"
|
|
2101
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2102
|
+
className: "dsgc-cdesc",
|
|
2103
|
+
children: "窗口外消息折成的已定 / 否决 / 未决,供后续角色接着用。"
|
|
2104
|
+
})]
|
|
2105
|
+
}),
|
|
2106
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ClipWell, {
|
|
2107
|
+
maxHeight: 160,
|
|
2108
|
+
className: "dsgc-clist",
|
|
2109
|
+
watch: open,
|
|
2110
|
+
children: [head.map((c, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConstraintRow, { item: c }, i)), overflow ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Fold, {
|
|
2111
|
+
open,
|
|
2112
|
+
children: extra.map((c, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConstraintRow, { item: c }, i + 3))
|
|
2113
|
+
}) : null]
|
|
2114
|
+
}),
|
|
2115
|
+
overflow ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2116
|
+
type: "button",
|
|
2117
|
+
className: "dsgc-cmore",
|
|
2118
|
+
onClick: () => {
|
|
2119
|
+
setOpen(!open);
|
|
2120
|
+
},
|
|
2121
|
+
"aria-expanded": open,
|
|
2122
|
+
children: open ? "收起" : "还有 " + rest + " 条约束"
|
|
2123
|
+
}) : null
|
|
2124
|
+
]
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2127
|
+
//#endregion
|
|
1459
2128
|
//#region src/client/components/MessageFlow.tsx
|
|
1460
2129
|
function MessageFlow(props) {
|
|
1461
|
-
const { snap, sess, busyNow, msgById, action } = props;
|
|
2130
|
+
const { snap, sess, busyNow, msgById, action, onRetrySpeak } = props;
|
|
1462
2131
|
const bubbles = [];
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
2132
|
+
const replaceId = busyNow ? snap.run.replaceMessageId : null;
|
|
2133
|
+
const replaceMsg = replaceId ? msgById[replaceId] : null;
|
|
2134
|
+
const liveRoleId = snap.run.currentRoleId || replaceMsg && (replaceMsg.failedRoleId || replaceMsg.speaker) || null;
|
|
2135
|
+
const lr = busyNow && liveRoleId ? roleById(snap, liveRoleId) : null;
|
|
2136
|
+
const live = lr ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2137
|
+
className: "dsgc-msg live",
|
|
2138
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2139
|
+
className: "dsgc-avatar",
|
|
2140
|
+
style: {
|
|
2141
|
+
border: "2px solid " + (lr.color || "#888"),
|
|
2142
|
+
"--role-color": lr.color || "#888"
|
|
2143
|
+
},
|
|
2144
|
+
children: lr.name.slice(0, 1)
|
|
2145
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2146
|
+
className: "dsgc-msgbody",
|
|
2147
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2148
|
+
className: "dsgc-msghead",
|
|
2149
|
+
children: [
|
|
2150
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2151
|
+
className: "dsgc-msgname",
|
|
2152
|
+
children: lr.name
|
|
2153
|
+
}),
|
|
2154
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2155
|
+
className: "dsgc-msgmodel",
|
|
2156
|
+
children: [
|
|
2157
|
+
lr.provider,
|
|
2158
|
+
" / ",
|
|
2159
|
+
lr.model
|
|
2160
|
+
]
|
|
2161
|
+
}),
|
|
2162
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2163
|
+
className: "dsgc-msgtime dsgc-typing",
|
|
2164
|
+
children: "深度求索..."
|
|
2165
|
+
})
|
|
2166
|
+
]
|
|
1483
2167
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1484
|
-
className: "dsgc-
|
|
1485
|
-
children: [
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
snap.run.partial ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
|
|
1513
|
-
text: snap.run.partial,
|
|
1514
|
-
streaming: true,
|
|
1515
|
-
labels: MD_LABELS
|
|
1516
|
-
}) : null,
|
|
1517
|
-
!snap.run.partial && !snap.run.partialReasoning ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1518
|
-
className: "dsgc-pending",
|
|
1519
|
-
children: [
|
|
1520
|
-
Icon(_deepseek_ai_dsh_client_ui_primitives.IconThinkOutline14, 14),
|
|
1521
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "思考中" }),
|
|
1522
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1523
|
-
className: "dsgc-pendingdots",
|
|
1524
|
-
"aria-hidden": "true",
|
|
1525
|
-
children: [
|
|
1526
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {}),
|
|
1527
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {}),
|
|
1528
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {})
|
|
1529
|
-
]
|
|
1530
|
-
})
|
|
1531
|
-
]
|
|
1532
|
-
}) : null
|
|
1533
|
-
]
|
|
1534
|
-
})]
|
|
2168
|
+
className: "dsgc-msgtext live",
|
|
2169
|
+
children: [
|
|
2170
|
+
snap.run.partialReasoning ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThinkRow, {
|
|
2171
|
+
text: snap.run.partialReasoning,
|
|
2172
|
+
running: true
|
|
2173
|
+
}) : null,
|
|
2174
|
+
snap.run.partial ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, {
|
|
2175
|
+
text: snap.run.partial,
|
|
2176
|
+
streaming: true,
|
|
2177
|
+
labels: MD_LABELS
|
|
2178
|
+
}) : null,
|
|
2179
|
+
!snap.run.partial && !snap.run.partialReasoning ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2180
|
+
className: "dsgc-pending",
|
|
2181
|
+
children: [
|
|
2182
|
+
Icon(_deepseek_ai_dsh_client_ui_primitives.IconThinkOutline14, 14),
|
|
2183
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "思考中" }),
|
|
2184
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2185
|
+
className: "dsgc-pendingdots",
|
|
2186
|
+
"aria-hidden": "true",
|
|
2187
|
+
children: [
|
|
2188
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {}),
|
|
2189
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {}),
|
|
2190
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {})
|
|
2191
|
+
]
|
|
2192
|
+
})
|
|
2193
|
+
]
|
|
2194
|
+
}) : null
|
|
2195
|
+
]
|
|
1535
2196
|
})]
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
2197
|
+
})]
|
|
2198
|
+
}, "__live") : null;
|
|
1538
2199
|
const pc = busyNow && snap.run.pendingConfirm && sess && snap.run.sessionId === sess.id ? snap.run.pendingConfirm : null;
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
2200
|
+
const confirm = pc ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2201
|
+
className: "dsgc-confirm",
|
|
2202
|
+
children: [
|
|
2203
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2204
|
+
className: "dsgc-confirmtitle",
|
|
2205
|
+
children: [Icon(_deepseek_ai_dsh_client_ui_primitives.IconWarningOutline16, 14), (lr ? lr.name : "角色") + " 请求执行命令(工作区内)"]
|
|
2206
|
+
}),
|
|
2207
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2208
|
+
className: "dsgc-confirmcmd",
|
|
2209
|
+
children: String(pc.args && pc.args.command || "")
|
|
2210
|
+
}),
|
|
2211
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2212
|
+
className: "dsgc-hint",
|
|
2213
|
+
children: "允许后将在工作区目录执行;拒绝后角色将继续纯文本讨论"
|
|
2214
|
+
}),
|
|
2215
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2216
|
+
className: "dsgc-confirmops",
|
|
2217
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2218
|
+
variant: "primary",
|
|
2219
|
+
size: "sm",
|
|
2220
|
+
onClick: () => {
|
|
2221
|
+
action({
|
|
2222
|
+
kind: "confirmCommand",
|
|
2223
|
+
toolCallId: pc.toolCallId,
|
|
2224
|
+
allow: true
|
|
2225
|
+
});
|
|
2226
|
+
},
|
|
2227
|
+
children: "允许"
|
|
2228
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2229
|
+
variant: "outline",
|
|
2230
|
+
size: "sm",
|
|
2231
|
+
onClick: () => {
|
|
2232
|
+
action({
|
|
2233
|
+
kind: "confirmCommand",
|
|
2234
|
+
toolCallId: pc.toolCallId,
|
|
2235
|
+
allow: false
|
|
2236
|
+
});
|
|
2237
|
+
},
|
|
2238
|
+
children: "拒绝"
|
|
2239
|
+
})]
|
|
2240
|
+
})
|
|
2241
|
+
]
|
|
2242
|
+
}, "__confirm") : null;
|
|
2243
|
+
let livePlaced = false;
|
|
2244
|
+
if (sess) {
|
|
2245
|
+
const groupRoles = snap.roles.filter((r) => r.groupId === sess.groupId);
|
|
2246
|
+
const ids = sess.messageIds;
|
|
2247
|
+
const memo = sess.constraints && sess.constraints.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConstraintList, { items: sess.constraints }, sess.id + "-constraints") : null;
|
|
2248
|
+
const foldAt = memo && ids.length > 40 ? ids.length - 40 : 0;
|
|
2249
|
+
if (memo && foldAt === 0) bubbles.push(memo);
|
|
2250
|
+
for (let i = 0; i < ids.length; i++) {
|
|
2251
|
+
if (memo && foldAt > 0 && i === foldAt) bubbles.push(memo);
|
|
2252
|
+
const m = msgById[ids[i]];
|
|
2253
|
+
if (!m) continue;
|
|
2254
|
+
const role = resolveFailedRole(m, groupRoles) || (m.speaker !== "user" && m.speaker !== "system" ? roleById(snap, m.speaker) : null);
|
|
2255
|
+
if (replaceId && m.id === replaceId) {
|
|
2256
|
+
if (live) bubbles.push(live);
|
|
2257
|
+
if (confirm) bubbles.push(confirm);
|
|
2258
|
+
livePlaced = true;
|
|
2259
|
+
continue;
|
|
2260
|
+
}
|
|
2261
|
+
bubbles.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bubble, {
|
|
2262
|
+
m,
|
|
2263
|
+
role,
|
|
2264
|
+
busy: !!snap.run.running,
|
|
2265
|
+
onRetry: onRetrySpeak
|
|
2266
|
+
}, m.id));
|
|
2267
|
+
}
|
|
1584
2268
|
}
|
|
2269
|
+
if (live && !livePlaced) bubbles.push(live);
|
|
2270
|
+
if (confirm && !livePlaced) bubbles.push(confirm);
|
|
1585
2271
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: bubbles });
|
|
1586
2272
|
}
|
|
1587
2273
|
//#endregion
|
|
1588
|
-
//#region src/core/types.ts
|
|
1589
|
-
/** 全部合法档位(展示顺序)。 */
|
|
1590
|
-
const PERMISSION_TIERS = [
|
|
1591
|
-
"view_only",
|
|
1592
|
-
"workspace_write",
|
|
1593
|
-
"full_access"
|
|
1594
|
-
];
|
|
1595
|
-
/** 档位安全化:合法字符串原样,其余 undefined。 */
|
|
1596
|
-
function asPermissionTier(value) {
|
|
1597
|
-
return typeof value === "string" && PERMISSION_TIERS.includes(value) ? value : void 0;
|
|
1598
|
-
}
|
|
1599
|
-
//#endregion
|
|
1600
2274
|
//#region src/client/components/PermissionSelect.tsx
|
|
1601
2275
|
/**
|
|
1602
2276
|
* 群组权限档位选择器(对齐主会话 composer 左下角的 /permission 选择器):
|
|
@@ -1790,6 +2464,8 @@ window.__ModuleLoader__.load({
|
|
|
1790
2464
|
}
|
|
1791
2465
|
//#endregion
|
|
1792
2466
|
//#region src/client/components/Composer.tsx
|
|
2467
|
+
/** 轮数控件 hover:宿主 Tooltip 三行说明(pre-line)。 */
|
|
2468
|
+
const ROUNDS_HINT = "一轮 = 参与角色各说一次。\n要他们自己互相反驳、你不插话时再加轮。\n要边看边插话,就留 1,再点发送。";
|
|
1793
2469
|
function Composer(props) {
|
|
1794
2470
|
const { snap, sess, group, enabledRoles, participants, mentionedRoles, busyNow, input, mention, mentionCandidates, mentionIdxC, fileCandidates, fileSearchError, fileSearchLoading, rounds, err, atBottom, bubblesLength, inputRef, scrollRef, setMentionIdx, setRounds, togglePart, onInputCE, onInputKeyDown, onPasteCE, onDropCE, onDragOverCE, insertChip, insertFileChip, sendMsg, stopRun, mutate, setMention } = props;
|
|
1795
2471
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1819,26 +2495,29 @@ window.__ModuleLoader__.load({
|
|
|
1819
2495
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1820
2496
|
className: "dsgc-partslabel",
|
|
1821
2497
|
children: "参与角色"
|
|
1822
|
-
}),
|
|
1823
|
-
className: "dsgc-
|
|
1824
|
-
children:
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
2498
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2499
|
+
className: "dsgc-partlist",
|
|
2500
|
+
children: mentionedRoles.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2501
|
+
className: "dsgc-partslabel",
|
|
2502
|
+
children: [
|
|
2503
|
+
"已 @ ",
|
|
2504
|
+
mentionedRoles.map((r) => r.name).join("、"),
|
|
2505
|
+
"(本轮仅被点名成员发言)"
|
|
2506
|
+
]
|
|
2507
|
+
}) : enabledRoles.length ? enabledRoles.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2508
|
+
type: "button",
|
|
2509
|
+
className: "dsgc-partchip" + (participants.includes(r.id) ? " on" : ""),
|
|
2510
|
+
onClick: () => togglePart(r.id),
|
|
2511
|
+
disabled: busyNow,
|
|
2512
|
+
title: busyNow ? "对话进行中,暂停调整" : "点击切换本轮是否参与",
|
|
2513
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2514
|
+
className: "dsgc-chipdot",
|
|
2515
|
+
style: { background: r.color || "#888" }
|
|
2516
|
+
}), r.name]
|
|
2517
|
+
}, r.id)) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2518
|
+
className: "dsgc-hint",
|
|
2519
|
+
children: "还没有启用的角色,请在右侧添加"
|
|
2520
|
+
})
|
|
1842
2521
|
})]
|
|
1843
2522
|
}),
|
|
1844
2523
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1970,9 +2649,12 @@ window.__ModuleLoader__.load({
|
|
|
1970
2649
|
}
|
|
1971
2650
|
}),
|
|
1972
2651
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
|
|
1973
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
2652
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(HoverTip, {
|
|
2653
|
+
label: ROUNDS_HINT,
|
|
2654
|
+
side: "top",
|
|
2655
|
+
delayMs: 500,
|
|
2656
|
+
maxWidth: 280,
|
|
1974
2657
|
className: "dsgc-rounds",
|
|
1975
|
-
title: "自由讨论的轮数(1–10):一轮 = 全体参与角色按顺序各发言一次",
|
|
1976
2658
|
children: [
|
|
1977
2659
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1978
2660
|
type: "button",
|
|
@@ -1986,7 +2668,6 @@ window.__ModuleLoader__.load({
|
|
|
1986
2668
|
}),
|
|
1987
2669
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1988
2670
|
className: "dsgc-roundnum",
|
|
1989
|
-
title: "轮数",
|
|
1990
2671
|
children: rounds
|
|
1991
2672
|
}),
|
|
1992
2673
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -2029,7 +2710,7 @@ window.__ModuleLoader__.load({
|
|
|
2029
2710
|
//#endregion
|
|
2030
2711
|
//#region src/client/components/ChatPanel.tsx
|
|
2031
2712
|
function ChatPanel(props) {
|
|
2032
|
-
const { snap, sess, group, enabledRoles, participants, mentionedRoles, busyNow, input, mention, mentionCandidates, mentionIdxC, fileCandidates, fileSearchError, fileSearchLoading, rounds, err, atBottom, topicDraft, msgById, navOpen, asideOpen, inputRef, scrollRef, setTopicDraft, setConfirmClear, setMentionIdx, setRounds, setNavOpen, setAsideOpen, togglePart, onMsgsScroll, onInputCE, onInputKeyDown, onPasteCE, onDropCE, onDragOverCE, insertChip, insertFileChip, sendMsg, stopRun, action, mutate, setMention } = props;
|
|
2713
|
+
const { snap, sess, group, enabledRoles, participants, mentionedRoles, busyNow, input, mention, mentionCandidates, mentionIdxC, fileCandidates, fileSearchError, fileSearchLoading, rounds, err, atBottom, topicDraft, msgById, navOpen, asideOpen, inputRef, scrollRef, setTopicDraft, setConfirmClear, setMentionIdx, setRounds, setNavOpen, setAsideOpen, togglePart, onMsgsScroll, onInputCE, onInputKeyDown, onPasteCE, onDropCE, onDragOverCE, insertChip, insertFileChip, sendMsg, stopRun, action, mutate, setMention, onRetrySpeak } = props;
|
|
2033
2714
|
const commitTopic = () => {
|
|
2034
2715
|
if (topicDraft !== null && sess && topicDraft !== sess.topic) mutate({
|
|
2035
2716
|
op: "setTopic",
|
|
@@ -2043,7 +2724,8 @@ window.__ModuleLoader__.load({
|
|
|
2043
2724
|
sess,
|
|
2044
2725
|
busyNow,
|
|
2045
2726
|
msgById,
|
|
2046
|
-
action
|
|
2727
|
+
action,
|
|
2728
|
+
onRetrySpeak
|
|
2047
2729
|
});
|
|
2048
2730
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
2049
2731
|
className: "dsgc-chat",
|
|
@@ -2070,37 +2752,40 @@ window.__ModuleLoader__.load({
|
|
|
2070
2752
|
},
|
|
2071
2753
|
children: Icon(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, 16)
|
|
2072
2754
|
}),
|
|
2073
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2755
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2074
2756
|
className: "dsgc-chathead",
|
|
2075
|
-
children:
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2757
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2758
|
+
className: "dsgc-chathead-row",
|
|
2759
|
+
children: [
|
|
2760
|
+
sess ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2761
|
+
className: "dsgc-sess-title",
|
|
2762
|
+
title: "当前会话:" + sess.name,
|
|
2763
|
+
children: sess.name
|
|
2764
|
+
}) : null,
|
|
2765
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2766
|
+
className: "dsgc-topic",
|
|
2767
|
+
value: topicDraft === null ? sess ? sess.topic : "" : topicDraft,
|
|
2768
|
+
placeholder: "设置本会话主题(可选)…",
|
|
2769
|
+
onChange: (e) => {
|
|
2770
|
+
setTopicDraft(e.target.value);
|
|
2771
|
+
},
|
|
2772
|
+
onBlur: commitTopic,
|
|
2773
|
+
onKeyDown: (e) => {
|
|
2774
|
+
if (e.key === "Enter") e.target.blur();
|
|
2775
|
+
}
|
|
2776
|
+
}),
|
|
2777
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2778
|
+
variant: "ghost",
|
|
2779
|
+
size: "sm",
|
|
2780
|
+
className: "dsgc-clearbtn",
|
|
2781
|
+
title: "清空当前会话的消息记录",
|
|
2782
|
+
onClick: () => {
|
|
2783
|
+
if (sess) setConfirmClear(true);
|
|
2784
|
+
},
|
|
2785
|
+
children: "清空"
|
|
2786
|
+
})
|
|
2787
|
+
]
|
|
2788
|
+
})
|
|
2104
2789
|
}),
|
|
2105
2790
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2106
2791
|
className: "dsgc-msgs",
|
|
@@ -2161,6 +2846,41 @@ window.__ModuleLoader__.load({
|
|
|
2161
2846
|
});
|
|
2162
2847
|
}
|
|
2163
2848
|
//#endregion
|
|
2849
|
+
//#region src/client/lib/composer-draft.ts
|
|
2850
|
+
const LIMIT = 32;
|
|
2851
|
+
const drafts = /* @__PURE__ */ new Map();
|
|
2852
|
+
const empty = () => ({
|
|
2853
|
+
html: "",
|
|
2854
|
+
text: ""
|
|
2855
|
+
});
|
|
2856
|
+
/** 读指定会话草稿;命中则提到 LRU 最近端。 */
|
|
2857
|
+
function readComposerDraft(sessionId) {
|
|
2858
|
+
if (!sessionId) return empty();
|
|
2859
|
+
const hit = drafts.get(sessionId);
|
|
2860
|
+
if (!hit) return empty();
|
|
2861
|
+
drafts.delete(sessionId);
|
|
2862
|
+
drafts.set(sessionId, hit);
|
|
2863
|
+
return hit;
|
|
2864
|
+
}
|
|
2865
|
+
/** 写入;空草稿删槽。超出上限淘汰最旧槽。 */
|
|
2866
|
+
function writeComposerDraft(sessionId, html, text) {
|
|
2867
|
+
if (!sessionId) return;
|
|
2868
|
+
drafts.delete(sessionId);
|
|
2869
|
+
if (!html && !text) return;
|
|
2870
|
+
drafts.set(sessionId, {
|
|
2871
|
+
html,
|
|
2872
|
+
text
|
|
2873
|
+
});
|
|
2874
|
+
if (drafts.size > LIMIT) {
|
|
2875
|
+
const oldest = drafts.keys().next().value;
|
|
2876
|
+
if (oldest !== void 0) drafts.delete(oldest);
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
/** 发送成功或明确丢弃时摘槽。 */
|
|
2880
|
+
function clearComposerDraft(sessionId) {
|
|
2881
|
+
if (sessionId) drafts.delete(sessionId);
|
|
2882
|
+
}
|
|
2883
|
+
//#endregion
|
|
2164
2884
|
//#region src/client/hooks/useGroupChatState.ts
|
|
2165
2885
|
/**
|
|
2166
2886
|
* 群聊面板核心状态管理 hook
|
|
@@ -2168,8 +2888,8 @@ window.__ModuleLoader__.load({
|
|
|
2168
2888
|
*/
|
|
2169
2889
|
/**
|
|
2170
2890
|
* 面板切换缓存(模块级,跨挂载存活):main 为 keyed 槽,主会话⇄群聊切换会
|
|
2171
|
-
*
|
|
2172
|
-
*
|
|
2891
|
+
* 整体卸载重挂面板组件;缓存最近快照、选中项与按会话分槽的未发送草稿,
|
|
2892
|
+
* 让重挂载瞬时恢复上次视图。挂载后的 state 拉取 / SSE 首帧再行校正。
|
|
2173
2893
|
*/
|
|
2174
2894
|
let cachedSnap = null;
|
|
2175
2895
|
let cachedGid = null;
|
|
@@ -2204,15 +2924,12 @@ window.__ModuleLoader__.load({
|
|
|
2204
2924
|
const [confirmDel, setConfirmDel] = (0, react.useState)(null);
|
|
2205
2925
|
const [confirmClear, setConfirmClear] = (0, react.useState)(false);
|
|
2206
2926
|
const [topicDraft, setTopicDraft] = (0, react.useState)(null);
|
|
2207
|
-
const [wsDraft, setWsDraft] = (0, react.useState)(null);
|
|
2208
2927
|
const [roleDraft, setRoleDraft] = (0, react.useState)(null);
|
|
2209
|
-
const [roleFormError, setRoleFormError] = (0, react.useState)("");
|
|
2210
2928
|
const [models, setModels] = (0, react.useState)(null);
|
|
2211
2929
|
const [modelsError, setModelsError] = (0, react.useState)(null);
|
|
2212
|
-
const [fileBrowser, setFileBrowser] = (0, react.useState)(null);
|
|
2213
2930
|
const [partsSel, setPartsSel] = (0, react.useState)(null);
|
|
2214
2931
|
const [rounds, setRounds] = (0, react.useState)(1);
|
|
2215
|
-
const [input, setInput] = (0, react.useState)(
|
|
2932
|
+
const [input, setInput] = (0, react.useState)(() => readComposerDraft(cachedSid).text);
|
|
2216
2933
|
const [mention, setMention] = (0, react.useState)(null);
|
|
2217
2934
|
const [mentionIdx, setMentionIdx] = (0, react.useState)(0);
|
|
2218
2935
|
const [err, setErr] = (0, react.useState)("");
|
|
@@ -2289,16 +3006,10 @@ window.__ModuleLoader__.load({
|
|
|
2289
3006
|
setConfirmClear,
|
|
2290
3007
|
roleDraft,
|
|
2291
3008
|
setRoleDraft,
|
|
2292
|
-
roleFormError,
|
|
2293
|
-
setRoleFormError,
|
|
2294
3009
|
models,
|
|
2295
3010
|
setModels,
|
|
2296
3011
|
modelsError,
|
|
2297
3012
|
setModelsError,
|
|
2298
|
-
fileBrowser,
|
|
2299
|
-
setFileBrowser,
|
|
2300
|
-
wsDraft,
|
|
2301
|
-
setWsDraft,
|
|
2302
3013
|
partsSel,
|
|
2303
3014
|
setPartsSel,
|
|
2304
3015
|
rounds,
|
|
@@ -2331,7 +3042,7 @@ window.__ModuleLoader__.load({
|
|
|
2331
3042
|
/**
|
|
2332
3043
|
* Extract active @token at cursor position.
|
|
2333
3044
|
* Matches @ at word boundary (line start or after whitespace).
|
|
2334
|
-
*
|
|
3045
|
+
*
|
|
2335
3046
|
* @param line - Current line text
|
|
2336
3047
|
* @param cursor - Cursor offset in line
|
|
2337
3048
|
* @returns Token or undefined if no active @token at cursor
|
|
@@ -2389,10 +3100,10 @@ window.__ModuleLoader__.load({
|
|
|
2389
3100
|
};
|
|
2390
3101
|
return Array.from(root.childNodes).map(walk).join("");
|
|
2391
3102
|
}
|
|
2392
|
-
/** @角色芯片的 HTML(原子元素:contenteditable=false + draggable
|
|
2393
|
-
function chipHtml(role) {
|
|
3103
|
+
/** @角色芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删;fresh=插入后需营救选区,加 data-new 标记)。 */
|
|
3104
|
+
function chipHtml(role, fresh = false) {
|
|
2394
3105
|
const c = escapeHtml(role.color || "#888");
|
|
2395
|
-
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>";
|
|
2396
3107
|
}
|
|
2397
3108
|
/** 把宿主 FileTypeIcon 渲成静态 SVG,再塞进 insertHTML(与检索列表同一套字形)。 */
|
|
2398
3109
|
function fileTypeIconMarkup(path, kind) {
|
|
@@ -2414,10 +3125,10 @@ window.__ModuleLoader__.load({
|
|
|
2414
3125
|
root.unmount();
|
|
2415
3126
|
}
|
|
2416
3127
|
}
|
|
2417
|
-
/** @文件芯片的 HTML(原子元素:contenteditable=false + draggable
|
|
2418
|
-
function fileChipHtml(path, kind) {
|
|
3128
|
+
/** @文件芯片的 HTML(原子元素:contenteditable=false + draggable,退格整删;fresh 同 chipHtml)。 */
|
|
3129
|
+
function fileChipHtml(path, kind, fresh = false) {
|
|
2419
3130
|
const basename = path.split("/").pop() || path;
|
|
2420
|
-
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>";
|
|
2421
3132
|
}
|
|
2422
3133
|
/**
|
|
2423
3134
|
* 光标前的活跃 @token(角色或文件弹层触发判定)。
|
|
@@ -2460,12 +3171,16 @@ window.__ModuleLoader__.load({
|
|
|
2460
3171
|
}, [input, inputRef]);
|
|
2461
3172
|
return { onMsgsScroll };
|
|
2462
3173
|
}
|
|
2463
|
-
function useComposerInput(inputRef, setInput, setMention, setMentionIdx) {
|
|
2464
|
-
|
|
3174
|
+
function useComposerInput(inputRef, setInput, setMention, setMentionIdx, sessionId) {
|
|
3175
|
+
const sessionIdRef = (0, react.useRef)(sessionId);
|
|
3176
|
+
sessionIdRef.current = sessionId;
|
|
3177
|
+
/** 从 DOM 同步 input 状态(序列化),并写入当前会话草稿槽。 */
|
|
2465
3178
|
const syncFromDOM = (0, react.useCallback)(() => {
|
|
2466
3179
|
const el = inputRef.current;
|
|
2467
3180
|
if (!el) return;
|
|
2468
|
-
|
|
3181
|
+
const text = serializeInput(el);
|
|
3182
|
+
setInput(text);
|
|
3183
|
+
writeComposerDraft(sessionIdRef.current, el.innerHTML, text);
|
|
2469
3184
|
}, [inputRef, setInput]);
|
|
2470
3185
|
return {
|
|
2471
3186
|
syncFromDOM,
|
|
@@ -2493,98 +3208,89 @@ window.__ModuleLoader__.load({
|
|
|
2493
3208
|
}, [])
|
|
2494
3209
|
};
|
|
2495
3210
|
}
|
|
3211
|
+
/**
|
|
3212
|
+
* 按会话恢复草稿:切会话时先把当前 HTML 写入旧槽,再灌入新槽;
|
|
3213
|
+
* 面板重挂载(主会话⇄群聊)时 editor 是新节点,从模块缓存灌回。
|
|
3214
|
+
*/
|
|
3215
|
+
function useComposerDraft(sessionId, inputRef, setInput, setMention) {
|
|
3216
|
+
const prevIdRef = (0, react.useRef)(null);
|
|
3217
|
+
(0, react.useLayoutEffect)(() => {
|
|
3218
|
+
const el = inputRef.current;
|
|
3219
|
+
const prev = prevIdRef.current;
|
|
3220
|
+
const next = sessionId || null;
|
|
3221
|
+
if (el && prev && prev !== next) writeComposerDraft(prev, el.innerHTML, serializeInput(el));
|
|
3222
|
+
prevIdRef.current = next;
|
|
3223
|
+
if (!next) return;
|
|
3224
|
+
const draft = readComposerDraft(next);
|
|
3225
|
+
if (el) el.innerHTML = draft.html;
|
|
3226
|
+
setInput(draft.text);
|
|
3227
|
+
setMention(null);
|
|
3228
|
+
return () => {
|
|
3229
|
+
const node = inputRef.current;
|
|
3230
|
+
const id = prevIdRef.current;
|
|
3231
|
+
if (node && id) writeComposerDraft(id, node.innerHTML, serializeInput(node));
|
|
3232
|
+
};
|
|
3233
|
+
}, [
|
|
3234
|
+
sessionId,
|
|
3235
|
+
inputRef,
|
|
3236
|
+
setInput,
|
|
3237
|
+
setMention
|
|
3238
|
+
]);
|
|
3239
|
+
}
|
|
2496
3240
|
function useMentionChip(inputRef, setMention, setMentionIdx, syncFromDOM) {
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
}
|
|
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");
|
|
2522
3265
|
}
|
|
2523
3266
|
}
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
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]),
|
|
2543
3291
|
insertFileChip: (0, react.useCallback)((path, kind) => {
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
if (!el || !sel) return;
|
|
2547
|
-
if (!sel.anchorNode || !el.contains(sel.anchorNode)) return;
|
|
2548
|
-
el.focus({ preventScroll: true });
|
|
2549
|
-
const node = sel.anchorNode;
|
|
2550
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
2551
|
-
const text = node.nodeValue || "";
|
|
2552
|
-
const off = sel.anchorOffset;
|
|
2553
|
-
const before = text.slice(0, off);
|
|
2554
|
-
const m = /(?:^|\s)@("?)([^"\s@]*)$/.exec(before);
|
|
2555
|
-
if (m) {
|
|
2556
|
-
const prefixLen = m[1] ? 2 : 1;
|
|
2557
|
-
const queryLen = m[2].length;
|
|
2558
|
-
const start = off - prefixLen - queryLen;
|
|
2559
|
-
if (start >= 0) {
|
|
2560
|
-
const range = document.createRange();
|
|
2561
|
-
range.setStart(node, start);
|
|
2562
|
-
range.setEnd(node, off);
|
|
2563
|
-
sel.removeAllRanges();
|
|
2564
|
-
sel.addRange(range);
|
|
2565
|
-
execCommand("delete");
|
|
2566
|
-
}
|
|
2567
|
-
}
|
|
2568
|
-
}
|
|
2569
|
-
execCommand("insertHTML", fileChipHtml(path, kind).replace("class=\"dsgc-chipin dsgc-chipin-file\"", "class=\"dsgc-chipin dsgc-chipin-file\" data-new=\"\""));
|
|
2570
|
-
const chip = el.querySelector(".dsgc-chipin[data-new]");
|
|
2571
|
-
if (!chip) return;
|
|
2572
|
-
const after = document.createRange();
|
|
2573
|
-
after.setStartAfter(chip);
|
|
2574
|
-
after.collapse(true);
|
|
2575
|
-
sel.removeAllRanges();
|
|
2576
|
-
sel.addRange(after);
|
|
2577
|
-
execCommand("insertText", " ");
|
|
2578
|
-
chip.removeAttribute("data-new");
|
|
2579
|
-
setMention(null);
|
|
2580
|
-
setMentionIdx(0);
|
|
2581
|
-
syncFromDOM();
|
|
2582
|
-
}, [
|
|
2583
|
-
inputRef,
|
|
2584
|
-
setMention,
|
|
2585
|
-
setMentionIdx,
|
|
2586
|
-
syncFromDOM
|
|
2587
|
-
])
|
|
3292
|
+
insertChipHtml(fileChipHtml(path, kind, true));
|
|
3293
|
+
}, [insertChipHtml])
|
|
2588
3294
|
};
|
|
2589
3295
|
}
|
|
2590
3296
|
function useInputKeyboard(mention, mentionCandidates, mentionIdxC, setMentionIdx, insertChip, insertFileChip, fileCandidates, setMention, sendMsg) {
|
|
@@ -2799,14 +3505,15 @@ window.__ModuleLoader__.load({
|
|
|
2799
3505
|
* @module dsh-group-chat/client/panel
|
|
2800
3506
|
*/
|
|
2801
3507
|
function GroupChatPanel() {
|
|
2802
|
-
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();
|
|
2803
3509
|
const [toast, setToast] = (0, react.useState)(null);
|
|
2804
3510
|
const { onMsgsScroll } = useComposerEffects(inputRef, scrollRef, input, atBottom, snap);
|
|
2805
|
-
const { syncFromDOM, onInputCE, onPasteCE, onDragOverCE, onDropCE } = useComposerInput(inputRef, setInput, setMention, setMentionIdx);
|
|
2806
3511
|
let group = snap && gid ? groupById(snap, gid) : null;
|
|
2807
3512
|
if (!group && snap && snap.groups.length) group = snap.groups[0];
|
|
2808
3513
|
let sess = snap && sid ? sessById(snap, sid) : null;
|
|
2809
3514
|
if ((!sess || sess.groupId !== group?.id) && group && snap) sess = group.sessionIds.length ? sessById(snap, group.sessionIds[group.sessionIds.length - 1]) : null;
|
|
3515
|
+
const { syncFromDOM, onInputCE, onPasteCE, onDragOverCE, onDropCE } = useComposerInput(inputRef, setInput, setMention, setMentionIdx, sess ? sess.id : null);
|
|
3516
|
+
useComposerDraft(sess ? sess.id : null, inputRef, setInput, setMention);
|
|
2810
3517
|
const enabledRoles = group ? group.roleIds.map((id) => roleById(snap, id)).filter((r) => !!r && r.enabled) : [];
|
|
2811
3518
|
const participants = partsSel || enabledRoles.map((r) => r.id);
|
|
2812
3519
|
const busyNow = !!(sess && snap && snap.run.running && snap.run.sessionId === sess.id);
|
|
@@ -2823,11 +3530,11 @@ window.__ModuleLoader__.load({
|
|
|
2823
3530
|
if (busyNow || !sess || sendingRef.current) return;
|
|
2824
3531
|
sendingRef.current = true;
|
|
2825
3532
|
try {
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
if (!mentionedRoles.length) return;
|
|
3533
|
+
if (!participants.length && !mentionedRoles.length) {
|
|
3534
|
+
setErr("请至少选择一个参与角色(或在消息中 @成员)");
|
|
3535
|
+
return;
|
|
2830
3536
|
}
|
|
3537
|
+
const parts = mentionedRoles.length ? mentionedRoles.map((r) => r.id) : participants;
|
|
2831
3538
|
const res = await action({
|
|
2832
3539
|
kind: "send",
|
|
2833
3540
|
sessionId: sess.id,
|
|
@@ -2843,6 +3550,7 @@ window.__ModuleLoader__.load({
|
|
|
2843
3550
|
setMention(null);
|
|
2844
3551
|
setErr("");
|
|
2845
3552
|
setAtBottom(true);
|
|
3553
|
+
clearComposerDraft(sess.id);
|
|
2846
3554
|
syncFromDOM();
|
|
2847
3555
|
}
|
|
2848
3556
|
} finally {
|
|
@@ -2885,6 +3593,25 @@ window.__ModuleLoader__.load({
|
|
|
2885
3593
|
const has = participants.includes(rid);
|
|
2886
3594
|
setPartsSel(has ? participants.filter((x) => x !== rid) : participants.concat([rid]));
|
|
2887
3595
|
};
|
|
3596
|
+
const retrySpeak = async (messageId) => {
|
|
3597
|
+
if (!sess) return;
|
|
3598
|
+
if (busyNow || snap && snap.run.running) {
|
|
3599
|
+
setToast({
|
|
3600
|
+
text: "已有对话进行中,请先停止",
|
|
3601
|
+
seq: Date.now()
|
|
3602
|
+
});
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
const res = await action({
|
|
3606
|
+
kind: "retrySpeak",
|
|
3607
|
+
sessionId: sess.id,
|
|
3608
|
+
messageId
|
|
3609
|
+
});
|
|
3610
|
+
if (res && !res.ok && res.error) setToast({
|
|
3611
|
+
text: res.error,
|
|
3612
|
+
seq: Date.now()
|
|
3613
|
+
});
|
|
3614
|
+
};
|
|
2888
3615
|
const doClear = async () => {
|
|
2889
3616
|
if (!sess) return;
|
|
2890
3617
|
if (busyNow) {
|
|
@@ -2922,80 +3649,15 @@ window.__ModuleLoader__.load({
|
|
|
2922
3649
|
};
|
|
2923
3650
|
const openRoleEditor = async (role) => {
|
|
2924
3651
|
setErr("");
|
|
2925
|
-
setRoleFormError("");
|
|
2926
3652
|
if (!models || modelsError) await fetchModels();
|
|
2927
3653
|
setRoleDraft(role ? draftFromRole(role) : blankDraft());
|
|
2928
3654
|
};
|
|
2929
|
-
const saveRole = async () => {
|
|
2930
|
-
if (!roleDraft) return;
|
|
2931
|
-
if (!roleDraft.name.trim()) {
|
|
2932
|
-
setRoleFormError("角色名称不能为空");
|
|
2933
|
-
return;
|
|
2934
|
-
}
|
|
2935
|
-
if (!roleDraft.provider || !roleDraft.model) {
|
|
2936
|
-
setRoleFormError("请选择角色绑定的模型");
|
|
2937
|
-
return;
|
|
2938
|
-
}
|
|
2939
|
-
const res = await mutate({
|
|
2940
|
-
op: "upsertRole",
|
|
2941
|
-
groupId: group.id,
|
|
2942
|
-
role: roleDraft
|
|
2943
|
-
});
|
|
2944
|
-
if (res && res.ok && res.snapshot && !res.snapshot.error) {
|
|
2945
|
-
setRoleDraft(null);
|
|
2946
|
-
setRoleFormError("");
|
|
2947
|
-
}
|
|
2948
|
-
};
|
|
2949
3655
|
const stopRun = async () => {
|
|
2950
3656
|
await action({
|
|
2951
3657
|
kind: "stop",
|
|
2952
3658
|
sessionId: sess.id
|
|
2953
3659
|
});
|
|
2954
3660
|
};
|
|
2955
|
-
const openBrowser = async (path) => {
|
|
2956
|
-
setFileBrowser({
|
|
2957
|
-
open: true,
|
|
2958
|
-
loading: true,
|
|
2959
|
-
list: null,
|
|
2960
|
-
error: ""
|
|
2961
|
-
});
|
|
2962
|
-
try {
|
|
2963
|
-
const res = await api.action({
|
|
2964
|
-
kind: "browse",
|
|
2965
|
-
path: path || ""
|
|
2966
|
-
});
|
|
2967
|
-
if (res && res.ok) setFileBrowser({
|
|
2968
|
-
open: true,
|
|
2969
|
-
loading: false,
|
|
2970
|
-
list: res,
|
|
2971
|
-
error: ""
|
|
2972
|
-
});
|
|
2973
|
-
else setFileBrowser({
|
|
2974
|
-
open: true,
|
|
2975
|
-
loading: false,
|
|
2976
|
-
list: null,
|
|
2977
|
-
error: res && res.error || "浏览失败"
|
|
2978
|
-
});
|
|
2979
|
-
} catch (e) {
|
|
2980
|
-
setFileBrowser({
|
|
2981
|
-
open: true,
|
|
2982
|
-
loading: false,
|
|
2983
|
-
list: null,
|
|
2984
|
-
error: String(e && e.message || e)
|
|
2985
|
-
});
|
|
2986
|
-
}
|
|
2987
|
-
};
|
|
2988
|
-
const selectCurrentDir = () => {
|
|
2989
|
-
if (!fileBrowser || !fileBrowser.list || !group) return;
|
|
2990
|
-
const path = fileBrowser.list.path;
|
|
2991
|
-
mutate({
|
|
2992
|
-
op: "setWorkspaceDir",
|
|
2993
|
-
groupId: group.id,
|
|
2994
|
-
path
|
|
2995
|
-
});
|
|
2996
|
-
setWsDraft(null);
|
|
2997
|
-
setFileBrowser(null);
|
|
2998
|
-
};
|
|
2999
3661
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3000
3662
|
className: "dsgc-root",
|
|
3001
3663
|
children: [
|
|
@@ -3063,20 +3725,17 @@ window.__ModuleLoader__.load({
|
|
|
3063
3725
|
stopRun,
|
|
3064
3726
|
action,
|
|
3065
3727
|
mutate,
|
|
3066
|
-
setMention
|
|
3728
|
+
setMention,
|
|
3729
|
+
onRetrySpeak: (messageId) => {
|
|
3730
|
+
retrySpeak(messageId);
|
|
3731
|
+
}
|
|
3067
3732
|
}),
|
|
3068
3733
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AsidePanel, {
|
|
3069
3734
|
snap,
|
|
3070
3735
|
group,
|
|
3071
3736
|
asideOpen,
|
|
3072
|
-
wsDraft,
|
|
3073
|
-
setWsDraft,
|
|
3074
|
-
fileBrowser,
|
|
3075
|
-
setFileBrowser,
|
|
3076
3737
|
mutate,
|
|
3077
|
-
openRoleEditor
|
|
3078
|
-
openBrowser,
|
|
3079
|
-
selectCurrentDir
|
|
3738
|
+
openRoleEditor
|
|
3080
3739
|
}),
|
|
3081
3740
|
sess && confirmClear ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
3082
3741
|
open: true,
|
|
@@ -3105,7 +3764,7 @@ window.__ModuleLoader__.load({
|
|
|
3105
3764
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
|
|
3106
3765
|
className: "dsgc-clearnotes",
|
|
3107
3766
|
children: [
|
|
3108
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "
|
|
3767
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "删除内容:本会话的用户消息与角色发言(含思考、工具调用记录)以及本会话结论/约束备忘,确认后立即落盘" }),
|
|
3109
3768
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "不可恢复:此操作没有回收站,也没有撤销" }),
|
|
3110
3769
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "不受影响:会话本身与主题、群成员角色、工作区目录、权限档位" })
|
|
3111
3770
|
]
|
|
@@ -3122,19 +3781,16 @@ window.__ModuleLoader__.load({
|
|
|
3122
3781
|
roleDraft ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RoleDrawer, {
|
|
3123
3782
|
draft: roleDraft,
|
|
3124
3783
|
set: setRoleDraft,
|
|
3784
|
+
groupId: group.id,
|
|
3125
3785
|
models,
|
|
3126
3786
|
modelsError,
|
|
3127
3787
|
onRetryModels: () => {
|
|
3128
3788
|
fetchModels();
|
|
3129
3789
|
},
|
|
3130
|
-
|
|
3131
|
-
saveRole();
|
|
3132
|
-
},
|
|
3790
|
+
mutate,
|
|
3133
3791
|
onCancel: () => {
|
|
3134
3792
|
setRoleDraft(null);
|
|
3135
|
-
|
|
3136
|
-
},
|
|
3137
|
-
formError: roleFormError
|
|
3793
|
+
}
|
|
3138
3794
|
}) : null
|
|
3139
3795
|
]
|
|
3140
3796
|
});
|