@deepseek-ai/dsh-client-ui-message-feedback 0.1.3-alpha.2 → 0.1.5-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +15 -10
- package/README.zh.md +16 -11
- package/lib/client.js +568 -378
- package/lib/types/client/FeedbackDialog.d.ts +16 -0
- package/lib/types/client/MessageFeedbackActions.d.ts +8 -11
- package/lib/types/client/controller.d.ts +27 -30
- package/lib/types/client/dialog.d.ts +79 -0
- package/lib/types/client/index.d.ts +11 -10
- package/lib/types/client/locales.d.ts +27 -13
- package/lib/types/client/slots.d.ts +40 -20
- package/lib/types/client/surface.d.ts +30 -0
- package/package.json +20 -16
package/lib/client.js
CHANGED
|
@@ -6,8 +6,277 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
8
|
let react = require("react");
|
|
9
|
-
let react_dom = require("react-dom");
|
|
10
9
|
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
10
|
+
let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
|
|
11
|
+
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-message-feedback/src/client/FeedbackDialog.module.css.mjs
|
|
12
|
+
const css$1 = "._6_jNJa_dialog._6_jNJa_dialog{border-radius:18px;gap:38px;width:min(488px,100%)}._6_jNJa_categories{flex-wrap:wrap;gap:8px;margin-top:-14px;display:flex}._6_jNJa_chip{border:.5px solid var(--dsw-alias-border-l4);corner-shape:round;height:28px;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;background:0 0;border-radius:14px;padding:0 12px;font-size:13px;line-height:18px;transition:background-color .12s,border-color .12s,color .12s}._6_jNJa_chip:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}._6_jNJa_chip:disabled{cursor:default;opacity:.4}._6_jNJa_chip:focus-visible{outline:2px solid var(--dsw-alias-button-primary-fill);outline-offset:2px}._6_jNJa_chipActive,._6_jNJa_chipActive:hover:not(:disabled){border-color:var(--dsw-alias-button-primary-fill);background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}._6_jNJa_detail{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-1);width:100%;min-height:116px;max-height:280px;color:var(--dsw-alias-label-primary);font:inherit;field-sizing:content;resize:none;border-radius:16px;margin-top:18px;padding:12px 14px;font-size:14px;line-height:22px;transition:border-color .12s,box-shadow .12s;display:block}._6_jNJa_detail::placeholder{color:var(--dsw-alias-label-caption)}._6_jNJa_detail:focus{border-color:var(--dsw-alias-border-l3);box-shadow:0 0 0 1px var(--dsw-alias-border-l3);outline:none}._6_jNJa_failure{color:var(--dsw-alias-label-tertiary);margin-top:8px;font-size:13px;line-height:20px;display:block}._6_jNJa_submit{border-radius:18px;width:100%;height:44px;font-size:14px;font-weight:500}._6_jNJa_toastIcon{border:1.5px solid var(--dsw-alias-state-success-primary);corner-shape:round;width:18px;height:18px;color:var(--dsw-alias-state-success-primary);border-radius:50%;place-items:center;display:grid}@media (prefers-reduced-motion:reduce){._6_jNJa_chip,._6_jNJa_detail{transition:none}}";
|
|
13
|
+
const tagId$1 = "@deepseek-ai/dsh-client-ui-message-feedback/FeedbackDialog.module.css";
|
|
14
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
15
|
+
const tag = document.createElement("style");
|
|
16
|
+
tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-message-feedback";
|
|
17
|
+
tag.dataset.pluginCss = tagId$1;
|
|
18
|
+
tag.textContent = css$1;
|
|
19
|
+
document.head.appendChild(tag);
|
|
20
|
+
}
|
|
21
|
+
var FeedbackDialog_module_css_default = {
|
|
22
|
+
"categories": "_6_jNJa_categories",
|
|
23
|
+
"chip": "_6_jNJa_chip",
|
|
24
|
+
"chipActive": "_6_jNJa_chipActive",
|
|
25
|
+
"detail": "_6_jNJa_detail",
|
|
26
|
+
"dialog": "_6_jNJa_dialog",
|
|
27
|
+
"failure": "_6_jNJa_failure",
|
|
28
|
+
"submit": "_6_jNJa_submit",
|
|
29
|
+
"toastIcon": "_6_jNJa_toastIcon"
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region lib/types/client/FeedbackDialog.js
|
|
33
|
+
/**
|
|
34
|
+
* The feedback dialog and its acknowledgement toast, rendered as one entry
|
|
35
|
+
* of `conversation.input.overlay` so each Session owns exactly one of each.
|
|
36
|
+
* The Modal and the Toast both portal to `document.body`; the overlay slot
|
|
37
|
+
* only supplies the per-session controller and the composer card the toast
|
|
38
|
+
* centers over.
|
|
39
|
+
* @module @deepseek-ai/dsh-client-ui-message-feedback/client/FeedbackDialog
|
|
40
|
+
*/
|
|
41
|
+
const CATEGORIES = Object.keys({
|
|
42
|
+
"task-result": true,
|
|
43
|
+
"instruction-following": true,
|
|
44
|
+
"product-interaction": true,
|
|
45
|
+
"service-stability": true,
|
|
46
|
+
"resource-cost": true,
|
|
47
|
+
"security-privacy-permission": true,
|
|
48
|
+
"other": true
|
|
49
|
+
});
|
|
50
|
+
/** Failure codes with their own copy; every other code reads the generic line. */
|
|
51
|
+
const FAILURE_COPY = {
|
|
52
|
+
"version-conflict": "error.conflict",
|
|
53
|
+
"note-too-large": "error.noteTooLarge"
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Render one Session's feedback dialog and toast.
|
|
57
|
+
* @param props - the dialog hook, the draft verbs, and the locale seat.
|
|
58
|
+
* @returns the modal while a target is open, the toast while one is showing.
|
|
59
|
+
*/
|
|
60
|
+
function FeedbackDialog({ useDialog, edit, submit, dismiss, dismissToast, t }) {
|
|
61
|
+
const state = useDialog((s) => s);
|
|
62
|
+
const probeRef = (0, react.useRef)(null);
|
|
63
|
+
const [card, setCard] = (0, react.useState)(null);
|
|
64
|
+
(0, react.useLayoutEffect)(() => {
|
|
65
|
+
setCard(probeRef.current?.closest("[data-composer-card]") ?? null);
|
|
66
|
+
}, []);
|
|
67
|
+
const toast = state.toast;
|
|
68
|
+
const onToastDone = (0, react.useCallback)(() => {
|
|
69
|
+
dismissToast(toast);
|
|
70
|
+
}, [dismissToast, toast]);
|
|
71
|
+
(0, react.useEffect)(() => () => {
|
|
72
|
+
dismissToast(toast);
|
|
73
|
+
}, [dismissToast, toast]);
|
|
74
|
+
const failure = state.failure === null ? null : t(FAILURE_COPY[state.failure] ?? "error.generic");
|
|
75
|
+
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
76
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
77
|
+
ref: probeRef,
|
|
78
|
+
hidden: true
|
|
79
|
+
}),
|
|
80
|
+
toast > 0 && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Toast, {
|
|
81
|
+
text: t("toast.recorded"),
|
|
82
|
+
icon: (0, react_jsx_runtime.jsx)("span", {
|
|
83
|
+
className: FeedbackDialog_module_css_default.toastIcon,
|
|
84
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 12 })
|
|
85
|
+
}),
|
|
86
|
+
anchor: card,
|
|
87
|
+
onDone: onToastDone
|
|
88
|
+
}, toast),
|
|
89
|
+
(0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
|
|
90
|
+
open: state.target !== null,
|
|
91
|
+
title: t("dialog.title"),
|
|
92
|
+
closeLabel: t("close"),
|
|
93
|
+
onClose: dismiss,
|
|
94
|
+
className: FeedbackDialog_module_css_default.dialog,
|
|
95
|
+
footer: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
96
|
+
variant: "primary",
|
|
97
|
+
className: FeedbackDialog_module_css_default.submit,
|
|
98
|
+
disabled: state.submitting,
|
|
99
|
+
onClick: () => {
|
|
100
|
+
submit();
|
|
101
|
+
},
|
|
102
|
+
children: state.submitting ? t("submitting") : t("submit")
|
|
103
|
+
}),
|
|
104
|
+
children: [
|
|
105
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
106
|
+
className: FeedbackDialog_module_css_default.categories,
|
|
107
|
+
role: "group",
|
|
108
|
+
"aria-label": t("dialog.categories"),
|
|
109
|
+
children: CATEGORIES.map((category) => (0, react_jsx_runtime.jsx)("button", {
|
|
110
|
+
type: "button",
|
|
111
|
+
className: state.category === category ? `${FeedbackDialog_module_css_default.chip} ${FeedbackDialog_module_css_default.chipActive}` : FeedbackDialog_module_css_default.chip,
|
|
112
|
+
"aria-pressed": state.category === category,
|
|
113
|
+
disabled: state.submitting,
|
|
114
|
+
onClick: () => {
|
|
115
|
+
edit({ category: state.category === category ? null : category });
|
|
116
|
+
},
|
|
117
|
+
children: t(`category.${category}`)
|
|
118
|
+
}, category))
|
|
119
|
+
}),
|
|
120
|
+
(0, react_jsx_runtime.jsx)("textarea", {
|
|
121
|
+
className: FeedbackDialog_module_css_default.detail,
|
|
122
|
+
"aria-label": t("dialog.detail"),
|
|
123
|
+
placeholder: t("dialog.hint"),
|
|
124
|
+
value: state.text,
|
|
125
|
+
readOnly: state.submitting,
|
|
126
|
+
onChange: (event) => {
|
|
127
|
+
edit({ text: event.target.value });
|
|
128
|
+
}
|
|
129
|
+
}),
|
|
130
|
+
failure !== null && (0, react_jsx_runtime.jsx)("span", {
|
|
131
|
+
className: FeedbackDialog_module_css_default.failure,
|
|
132
|
+
role: "status",
|
|
133
|
+
children: failure
|
|
134
|
+
})
|
|
135
|
+
]
|
|
136
|
+
})
|
|
137
|
+
] });
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-message-feedback/src/client/MessageFeedbackActions.module.css.mjs
|
|
141
|
+
const css = "._8_XoUG_action{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}._8_XoUG_action svg{width:calc(15px + var(--dsh-content-font-delta,0px));height:calc(15px + var(--dsh-content-font-delta,0px))}._8_XoUG_action:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}._8_XoUG_action:disabled{cursor:default;opacity:.4}._8_XoUG_action[data-active]{color:var(--dsw-alias-label-tertiary)}._8_XoUG_failure{color:var(--dsw-alias-label-tertiary);padding-left:4px;font-size:13px;line-height:20px}";
|
|
142
|
+
const tagId = "@deepseek-ai/dsh-client-ui-message-feedback/MessageFeedbackActions.module.css";
|
|
143
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
144
|
+
const tag = document.createElement("style");
|
|
145
|
+
tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-message-feedback";
|
|
146
|
+
tag.dataset.pluginCss = tagId;
|
|
147
|
+
tag.textContent = css;
|
|
148
|
+
document.head.appendChild(tag);
|
|
149
|
+
}
|
|
150
|
+
var MessageFeedbackActions_module_css_default = {
|
|
151
|
+
"action": "_8_XoUG_action",
|
|
152
|
+
"failure": "_8_XoUG_failure"
|
|
153
|
+
};
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region lib/types/client/MessageFeedbackActions.js
|
|
156
|
+
/**
|
|
157
|
+
* Per-message feedback controls: the Like/Dislike pair inside the assistant
|
|
158
|
+
* message's IconActions row, between copy and branch. Like records at once
|
|
159
|
+
* and raises the Session's acknowledgement toast; Dislike opens the Session's
|
|
160
|
+
* feedback dialog, whose submission records the negative judgment with its
|
|
161
|
+
* category and text. Clicking the recorded rating retracts it. A recorded
|
|
162
|
+
* rating shows the filled glyph so the signal survives a pointer leaving the row.
|
|
163
|
+
* @module @deepseek-ai/dsh-client-ui-message-feedback/client/MessageFeedbackActions
|
|
164
|
+
*/
|
|
165
|
+
/**
|
|
166
|
+
* One message's feedback controls.
|
|
167
|
+
* @param props - the owner's message identity, the injected verbs, and the
|
|
168
|
+
* shared feedback hook.
|
|
169
|
+
* @returns the rating buttons with any failure notice beside them.
|
|
170
|
+
*/
|
|
171
|
+
function MessageFeedbackActions({ messageId, ensure, current, toggle, openDialog, acknowledge, useFeedback, t }) {
|
|
172
|
+
const item = useFeedback((view) => view.items.get(messageId));
|
|
173
|
+
const loadFailed = useFeedback((view) => view.status === "error");
|
|
174
|
+
const rating = item?.rating;
|
|
175
|
+
const [pending, setPending] = (0, react.useState)(false);
|
|
176
|
+
const [failure, setFailure] = (0, react.useState)(null);
|
|
177
|
+
const seeded = (0, react.useRef)(false);
|
|
178
|
+
const seed = (0, react.useCallback)(() => {
|
|
179
|
+
if (seeded.current) return;
|
|
180
|
+
seeded.current = true;
|
|
181
|
+
ensure();
|
|
182
|
+
}, [ensure]);
|
|
183
|
+
const alive = (0, react.useRef)(true);
|
|
184
|
+
(0, react.useEffect)(() => () => {
|
|
185
|
+
alive.current = false;
|
|
186
|
+
}, []);
|
|
187
|
+
const errorCopy = (0, react.useCallback)((result) => {
|
|
188
|
+
return result.error.code === "version-conflict" ? t("error.conflict") : t("error.generic");
|
|
189
|
+
}, [t]);
|
|
190
|
+
const onLike = (0, react.useCallback)(() => {
|
|
191
|
+
setPending(true);
|
|
192
|
+
setFailure(null);
|
|
193
|
+
toggle(messageId, "positive").then((result) => {
|
|
194
|
+
if (!alive.current) return;
|
|
195
|
+
setPending(false);
|
|
196
|
+
if (!result.ok) {
|
|
197
|
+
setFailure(errorCopy(result));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (result.rating === "positive") acknowledge();
|
|
201
|
+
});
|
|
202
|
+
}, [
|
|
203
|
+
acknowledge,
|
|
204
|
+
errorCopy,
|
|
205
|
+
messageId,
|
|
206
|
+
toggle
|
|
207
|
+
]);
|
|
208
|
+
const onDislike = (0, react.useCallback)(() => {
|
|
209
|
+
setPending(true);
|
|
210
|
+
setFailure(null);
|
|
211
|
+
ensure().then((loaded) => {
|
|
212
|
+
if (!alive.current) return;
|
|
213
|
+
if (!loaded.ok || current(messageId)?.rating !== "negative") {
|
|
214
|
+
setPending(false);
|
|
215
|
+
openDialog(messageId);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
toggle(messageId, "negative").then((result) => {
|
|
219
|
+
if (!alive.current) return;
|
|
220
|
+
setPending(false);
|
|
221
|
+
if (!result.ok) setFailure(errorCopy(result));
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}, [
|
|
225
|
+
current,
|
|
226
|
+
ensure,
|
|
227
|
+
errorCopy,
|
|
228
|
+
messageId,
|
|
229
|
+
openDialog,
|
|
230
|
+
toggle
|
|
231
|
+
]);
|
|
232
|
+
const likeLabel = rating === "positive" ? t("action.likeActive") : t("action.like");
|
|
233
|
+
const dislikeLabel = rating === "negative" ? t("action.dislikeActive") : t("action.dislike");
|
|
234
|
+
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
235
|
+
(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
236
|
+
label: likeLabel,
|
|
237
|
+
side: "bottom",
|
|
238
|
+
children: (0, react_jsx_runtime.jsx)("button", {
|
|
239
|
+
type: "button",
|
|
240
|
+
className: MessageFeedbackActions_module_css_default.action,
|
|
241
|
+
"aria-label": likeLabel,
|
|
242
|
+
"aria-pressed": rating === "positive",
|
|
243
|
+
"data-active": rating === "positive" || void 0,
|
|
244
|
+
disabled: pending,
|
|
245
|
+
onFocus: seed,
|
|
246
|
+
onPointerEnter: seed,
|
|
247
|
+
onClick: onLike,
|
|
248
|
+
children: rating === "positive" ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLikeFill16, {}) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLikeOutline16, {})
|
|
249
|
+
})
|
|
250
|
+
}),
|
|
251
|
+
(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
252
|
+
label: dislikeLabel,
|
|
253
|
+
side: "bottom",
|
|
254
|
+
children: (0, react_jsx_runtime.jsx)("button", {
|
|
255
|
+
type: "button",
|
|
256
|
+
className: MessageFeedbackActions_module_css_default.action,
|
|
257
|
+
"aria-label": dislikeLabel,
|
|
258
|
+
"aria-pressed": rating === "negative",
|
|
259
|
+
"data-active": rating === "negative" || void 0,
|
|
260
|
+
disabled: pending,
|
|
261
|
+
onFocus: seed,
|
|
262
|
+
onPointerEnter: seed,
|
|
263
|
+
onClick: onDislike,
|
|
264
|
+
children: rating === "negative" ? (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDislikeFill16, {}) : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDislikeOutline16, {})
|
|
265
|
+
})
|
|
266
|
+
}),
|
|
267
|
+
failure === null && loadFailed && (0, react_jsx_runtime.jsx)("span", {
|
|
268
|
+
className: MessageFeedbackActions_module_css_default.failure,
|
|
269
|
+
role: "status",
|
|
270
|
+
children: t("error.load")
|
|
271
|
+
}),
|
|
272
|
+
failure !== null && (0, react_jsx_runtime.jsx)("span", {
|
|
273
|
+
className: MessageFeedbackActions_module_css_default.failure,
|
|
274
|
+
role: "status",
|
|
275
|
+
children: failure
|
|
276
|
+
})
|
|
277
|
+
] });
|
|
278
|
+
}
|
|
279
|
+
//#endregion
|
|
11
280
|
//#region lib/types/client/controller.js
|
|
12
281
|
const INITIAL_VIEW = Object.freeze({
|
|
13
282
|
status: "cold",
|
|
@@ -22,7 +291,11 @@ window.__ModuleLoader__.load({
|
|
|
22
291
|
message: "feedback controller is disposed"
|
|
23
292
|
})
|
|
24
293
|
});
|
|
25
|
-
/**
|
|
294
|
+
/**
|
|
295
|
+
* Human-readable text for one business failure code.
|
|
296
|
+
* @param code - the Host's business failure code.
|
|
297
|
+
* @returns the developer-facing description carried in the failure branch.
|
|
298
|
+
*/
|
|
26
299
|
function describe(code) {
|
|
27
300
|
switch (code) {
|
|
28
301
|
case "session-not-found": return "this session is no longer persisted";
|
|
@@ -123,22 +396,17 @@ window.__ModuleLoader__.load({
|
|
|
123
396
|
}
|
|
124
397
|
/**
|
|
125
398
|
* Create or replace feedback for one message, comparing against the version
|
|
126
|
-
* this controller last observed.
|
|
127
|
-
*
|
|
128
|
-
* The note is resolved here rather than by the caller: `mutate` awaits the
|
|
129
|
-
* one list read first, so this body always sees the committed item, while a
|
|
130
|
-
* control that rendered before that read completed would still be holding
|
|
131
|
-
* `undefined`. Omitting `note` therefore keeps whatever is stored; only
|
|
132
|
-
* {@link clearNote} removes one.
|
|
399
|
+
* this controller last observed. The item stores exactly `entry`: an entry
|
|
400
|
+
* without a note or category replaces whatever the stored item carried.
|
|
133
401
|
* @param messageId - target assistant message.
|
|
134
402
|
* @param rating - desired judgment.
|
|
135
|
-
* @param
|
|
403
|
+
* @param entry - explanation and category to store with the judgment.
|
|
136
404
|
* @returns the settled mutation result.
|
|
137
405
|
*/
|
|
138
|
-
rate(messageId, rating,
|
|
406
|
+
rate(messageId, rating, entry = {}) {
|
|
139
407
|
return this.mutate(async () => {
|
|
140
408
|
const observed = this.view.items.get(messageId);
|
|
141
|
-
return await this.putCommitted(messageId, rating,
|
|
409
|
+
return await this.putCommitted(messageId, rating, entry, observed);
|
|
142
410
|
});
|
|
143
411
|
}
|
|
144
412
|
/**
|
|
@@ -146,50 +414,31 @@ window.__ModuleLoader__.load({
|
|
|
146
414
|
* the committed rating already matches. The decision reads the committed item
|
|
147
415
|
* inside the serialized mutation, so a click that lands before the first list
|
|
148
416
|
* read still toggles against the stored value rather than the empty view a
|
|
149
|
-
* cold control rendered.
|
|
417
|
+
* cold control rendered. A replacement stores the bare judgment; the note
|
|
418
|
+
* and category of the judgment it replaces do not carry over.
|
|
150
419
|
* @param messageId - target assistant message.
|
|
151
420
|
* @param rating - the judgment the human asked for.
|
|
152
|
-
* @returns the settled mutation result.
|
|
421
|
+
* @returns the settled mutation result with the rating now committed.
|
|
153
422
|
*/
|
|
154
423
|
toggle(messageId, rating) {
|
|
155
424
|
return this.mutate(async () => {
|
|
156
425
|
const observed = this.view.items.get(messageId);
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
* @param messageId - target assistant message.
|
|
164
|
-
* @returns the settled mutation result.
|
|
165
|
-
*/
|
|
166
|
-
clearNote(messageId) {
|
|
167
|
-
return this.mutate(async () => {
|
|
168
|
-
const observed = this.view.items.get(messageId);
|
|
169
|
-
if (observed === void 0 || observed.note === void 0) return OK;
|
|
170
|
-
return await this.putCommitted(messageId, observed.rating, void 0, observed);
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Remove feedback for one message. A message with no known item is already
|
|
175
|
-
* in the requested state, so no call is made.
|
|
176
|
-
* @param messageId - target assistant message.
|
|
177
|
-
* @returns the settled mutation result.
|
|
178
|
-
*/
|
|
179
|
-
clear(messageId) {
|
|
180
|
-
return this.mutate(async () => {
|
|
181
|
-
const observed = this.view.items.get(messageId);
|
|
182
|
-
if (observed === void 0) return OK;
|
|
183
|
-
return await this.deleteCommitted(messageId, observed);
|
|
426
|
+
const retract = observed?.rating === rating;
|
|
427
|
+
const result = retract ? await this.deleteCommitted(messageId, observed) : await this.putCommitted(messageId, rating, {}, observed);
|
|
428
|
+
return result.ok ? {
|
|
429
|
+
ok: true,
|
|
430
|
+
rating: retract ? null : rating
|
|
431
|
+
} : result;
|
|
184
432
|
});
|
|
185
433
|
}
|
|
186
434
|
/** Commit one put against the observed version and reconcile a conflict. */
|
|
187
|
-
async putCommitted(messageId, rating,
|
|
435
|
+
async putCommitted(messageId, rating, entry, observed) {
|
|
188
436
|
const carried = await this.ctx.remote.messageFeedback.put({
|
|
189
437
|
sessionId: this.sessionId,
|
|
190
438
|
messageId,
|
|
191
439
|
rating,
|
|
192
|
-
...
|
|
440
|
+
...entry.text === void 0 ? {} : { note: entry.text },
|
|
441
|
+
...entry.category === void 0 ? {} : { category: entry.category },
|
|
193
442
|
ifVersion: observed?.version ?? null
|
|
194
443
|
});
|
|
195
444
|
if (!carried.ok) return carrierFailure(carried.error);
|
|
@@ -297,297 +546,185 @@ window.__ModuleLoader__.load({
|
|
|
297
546
|
}
|
|
298
547
|
};
|
|
299
548
|
//#endregion
|
|
300
|
-
//#region
|
|
301
|
-
const css = "._8_XoUG_action{width:calc(28px + var(--dsh-content-font-delta,0px));height:calc(28px + var(--dsh-content-font-delta,0px));color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:28px;justify-content:center;align-items:center;padding:6px;display:inline-flex}._8_XoUG_action svg{width:calc(15px + var(--dsh-content-font-delta,0px));height:calc(15px + var(--dsh-content-font-delta,0px))}._8_XoUG_action:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}._8_XoUG_action:disabled{cursor:default;opacity:.4}._8_XoUG_action[data-active]{color:var(--dsw-alias-label-primary)}._8_XoUG_noteOpen{max-width:220px;color:var(--dsw-alias-label-tertiary);font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(28px + var(--dsh-content-font-delta,0px));white-space:nowrap;text-overflow:ellipsis;cursor:pointer;background:0 0;border:none;border-radius:14px;padding:0 8px;overflow:hidden}._8_XoUG_noteOpen:hover,._8_XoUG_noteOpen[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}._8_XoUG_notePanel{z-index:1100;box-sizing:border-box;background:var(--dsw-specific-menu);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);width:320px;max-width:min(360px,100vw - 24px);max-height:calc(100vh - 24px);box-shadow:var(--dsw-elevation-prominent);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border:0;border-radius:12px;flex-direction:column;gap:8px;padding:8px;display:flex;position:fixed;overflow-y:auto}._8_XoUG_noteInput{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);background:var(--dsw-alias-bg-layer-1);width:100%;color:var(--dsw-alias-label-primary);font:inherit;resize:vertical;border-radius:8px;padding:6px 8px;font-size:13px}._8_XoUG_noteActions{justify-content:flex-end;gap:6px;display:flex}._8_XoUG_noteSave,._8_XoUG_noteCancel{cursor:pointer;border:none;border-radius:14px;height:28px;padding:0 10px;font-size:13px}._8_XoUG_noteSave{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}._8_XoUG_noteSave:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}._8_XoUG_noteSave:disabled{cursor:default;opacity:.4}._8_XoUG_noteCancel{color:var(--dsw-alias-label-tertiary);background:0 0}._8_XoUG_noteCancel:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}._8_XoUG_failure{color:var(--dsw-alias-label-tertiary);padding-left:4px;font-size:13px;line-height:20px}";
|
|
302
|
-
const tagId = "@deepseek-ai/dsh-client-ui-message-feedback/MessageFeedbackActions.module.css";
|
|
303
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
304
|
-
const tag = document.createElement("style");
|
|
305
|
-
tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-message-feedback";
|
|
306
|
-
tag.dataset.pluginCss = tagId;
|
|
307
|
-
tag.textContent = css;
|
|
308
|
-
document.head.appendChild(tag);
|
|
309
|
-
}
|
|
310
|
-
var MessageFeedbackActions_module_css_default = {
|
|
311
|
-
"action": "_8_XoUG_action",
|
|
312
|
-
"failure": "_8_XoUG_failure",
|
|
313
|
-
"noteActions": "_8_XoUG_noteActions",
|
|
314
|
-
"noteCancel": "_8_XoUG_noteCancel",
|
|
315
|
-
"noteInput": "_8_XoUG_noteInput",
|
|
316
|
-
"noteOpen": "_8_XoUG_noteOpen",
|
|
317
|
-
"notePanel": "_8_XoUG_notePanel",
|
|
318
|
-
"noteSave": "_8_XoUG_noteSave"
|
|
319
|
-
};
|
|
320
|
-
//#endregion
|
|
321
|
-
//#region lib/types/client/MessageFeedbackActions.js
|
|
549
|
+
//#region lib/types/client/dialog.js
|
|
322
550
|
/**
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
* conversation column. Portaling out of the column also escapes its `overflow`
|
|
330
|
-
* clip, so the panel cannot be cropped or detached from the message it annotates.
|
|
331
|
-
* @module @deepseek-ai/dsh-client-ui-message-feedback/client/MessageFeedbackActions
|
|
332
|
-
*/
|
|
333
|
-
/** Safe distance kept between the panel and the viewport edges (the Menu portal margin). */
|
|
334
|
-
const PANEL_MARGIN = 12;
|
|
335
|
-
/** Distance between the trigger's bottom edge and the panel's top. */
|
|
336
|
-
const PANEL_GAP = 4;
|
|
337
|
-
/**
|
|
338
|
-
* Unplaced portal panel: hidden but laid out so `offsetWidth` is real for the
|
|
339
|
-
* clamp. The explicit insets match `Menu`'s measure style — a `position: fixed`
|
|
340
|
-
* element with auto insets otherwise sits at its static position, a different
|
|
341
|
-
* origin than the one the first placement measures from.
|
|
551
|
+
* Headless state of one Session's feedback dialog and its acknowledgement
|
|
552
|
+
* toast. One form serves two targets: the Session itself (a bare `/feedback`)
|
|
553
|
+
* and one assistant message (Dislike). The overlay view renders from the
|
|
554
|
+
* store; the message controls raise the toast after a Like through
|
|
555
|
+
* {@link FeedbackDialogController.acknowledge}.
|
|
556
|
+
* @module @deepseek-ai/dsh-client-ui-message-feedback/client/dialog
|
|
342
557
|
*/
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
558
|
+
const CLOSED = {
|
|
559
|
+
target: null,
|
|
560
|
+
category: null,
|
|
561
|
+
text: "",
|
|
562
|
+
submitting: false,
|
|
563
|
+
failure: null
|
|
347
564
|
};
|
|
348
|
-
/**
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
alive.current = false;
|
|
376
|
-
}, []);
|
|
377
|
-
/** Bumped whenever an editing session ends, so a late save can tell it is stale. */
|
|
378
|
-
const noteGeneration = (0, react.useRef)(0);
|
|
379
|
-
/** Current panel open-state, readable from a stale closure via a ref. */
|
|
380
|
-
const noteOpenRef = (0, react.useRef)(false);
|
|
381
|
-
(0, react.useEffect)(() => {
|
|
382
|
-
noteOpenRef.current = noteOpen;
|
|
383
|
-
}, [noteOpen]);
|
|
384
|
-
const errorCopy = (0, react.useCallback)((result) => {
|
|
385
|
-
return result.error?.code === "version-conflict" ? t("error.conflict") : t("error.generic");
|
|
386
|
-
}, [t]);
|
|
387
|
-
const settleRating = (0, react.useCallback)((result) => {
|
|
388
|
-
if (!alive.current) return;
|
|
389
|
-
setPending(false);
|
|
390
|
-
setRowFailure(result.ok ? null : errorCopy(result));
|
|
391
|
-
}, [errorCopy]);
|
|
392
|
-
const closeNote = (0, react.useCallback)(() => {
|
|
393
|
-
noteGeneration.current += 1;
|
|
394
|
-
setNoteOpen(false);
|
|
395
|
-
}, []);
|
|
396
|
-
const onRate = (0, react.useCallback)((next) => {
|
|
397
|
-
setPending(true);
|
|
398
|
-
setRowFailure(null);
|
|
399
|
-
closeNote();
|
|
400
|
-
toggle(messageId, next).then(settleRating);
|
|
401
|
-
}, [
|
|
402
|
-
closeNote,
|
|
403
|
-
messageId,
|
|
404
|
-
settleRating,
|
|
405
|
-
toggle
|
|
406
|
-
]);
|
|
407
|
-
const onSaveNote = (0, react.useCallback)((current) => {
|
|
408
|
-
const trimmed = draft.trim();
|
|
409
|
-
setPending(true);
|
|
410
|
-
setNoteFailure(null);
|
|
411
|
-
const generation = noteGeneration.current;
|
|
412
|
-
const staleSeed = item?.note ?? "";
|
|
413
|
-
(trimmed.length === 0 ? clearNote(messageId) : rate(messageId, current, trimmed)).then((result) => {
|
|
414
|
-
if (!alive.current) return;
|
|
415
|
-
setPending(false);
|
|
416
|
-
if (result.ok) {
|
|
417
|
-
if (generation === noteGeneration.current) {
|
|
418
|
-
setNoteFailure(null);
|
|
419
|
-
setNoteOpen(false);
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
setDraft((draftNow) => draftNow === staleSeed ? trimmed : draftNow);
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
if (generation === noteGeneration.current || !noteOpenRef.current) setNoteFailure(errorCopy(result));
|
|
565
|
+
/** Per-session dialog controller; one instance backs the overlay entry and every message control. */
|
|
566
|
+
var FeedbackDialogController = class {
|
|
567
|
+
submit;
|
|
568
|
+
/** Dialog state store (the overlay entry subscribes here). */
|
|
569
|
+
state = (0, _deepseek_ai_dsh_client_store.createSnapshotStore)({
|
|
570
|
+
...CLOSED,
|
|
571
|
+
toast: 0
|
|
572
|
+
});
|
|
573
|
+
/** Bumped by every open, dismiss, and dispose so a late settlement can tell its draft is gone. */
|
|
574
|
+
generation = 0;
|
|
575
|
+
toastSeq = 0;
|
|
576
|
+
/**
|
|
577
|
+
* @param submit - records one submission; the owner routes it by target.
|
|
578
|
+
*/
|
|
579
|
+
constructor(submit) {
|
|
580
|
+
this.submit = submit;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Open the dialog with an empty draft, replacing any open draft.
|
|
584
|
+
* @param target - what the submission records against.
|
|
585
|
+
*/
|
|
586
|
+
open(target) {
|
|
587
|
+
this.generation += 1;
|
|
588
|
+
this.state.set({
|
|
589
|
+
...CLOSED,
|
|
590
|
+
target,
|
|
591
|
+
toast: this.state.getSnapshot().toast
|
|
426
592
|
});
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
593
|
+
}
|
|
594
|
+
/** Close the dialog and discard the draft; a toast on screen stays. */
|
|
595
|
+
dismiss() {
|
|
596
|
+
this.generation += 1;
|
|
597
|
+
this.state.set({
|
|
598
|
+
...CLOSED,
|
|
599
|
+
toast: this.state.getSnapshot().toast
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Replace part of the draft while it is editable.
|
|
604
|
+
* @param draft - the category (null clears it) or the text as typed.
|
|
605
|
+
*/
|
|
606
|
+
edit(draft) {
|
|
607
|
+
const s = this.state.getSnapshot();
|
|
608
|
+
if (s.target === null || s.submitting) return;
|
|
609
|
+
this.state.set({
|
|
610
|
+
...s,
|
|
611
|
+
...draft
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Submit the draft; an empty draft is a valid submission. Success closes the
|
|
616
|
+
* dialog and raises the toast; a failure keeps the dialog open with its code.
|
|
617
|
+
* @returns after the submission settles.
|
|
618
|
+
*/
|
|
619
|
+
async submitDraft() {
|
|
620
|
+
const s = this.state.getSnapshot();
|
|
621
|
+
if (s.target === null || s.submitting) return;
|
|
622
|
+
const generation = this.generation;
|
|
623
|
+
this.state.set({
|
|
624
|
+
...s,
|
|
625
|
+
submitting: true,
|
|
626
|
+
failure: null
|
|
627
|
+
});
|
|
628
|
+
const text = s.text.trim();
|
|
629
|
+
const result = await this.submit(s.target, {
|
|
630
|
+
...text.length === 0 ? {} : { text },
|
|
631
|
+
...s.category === null ? {} : { category: s.category }
|
|
632
|
+
});
|
|
633
|
+
if (result.ok) {
|
|
634
|
+
if (generation === this.generation) this.dismiss();
|
|
635
|
+
this.acknowledge();
|
|
439
636
|
return;
|
|
440
637
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
638
|
+
if (generation !== this.generation) return;
|
|
639
|
+
this.state.set({
|
|
640
|
+
...this.state.getSnapshot(),
|
|
641
|
+
submitting: false,
|
|
642
|
+
failure: result.error.code
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
/** Show the acknowledgement toast; a toast already on screen restarts. */
|
|
646
|
+
acknowledge() {
|
|
647
|
+
this.toastSeq += 1;
|
|
648
|
+
this.state.set({
|
|
649
|
+
...this.state.getSnapshot(),
|
|
650
|
+
toast: this.toastSeq
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Retire one toast after its fade; a newer toast is left alone.
|
|
655
|
+
* @param seq - the toast sequence the view finished showing.
|
|
656
|
+
*/
|
|
657
|
+
dismissToast(seq) {
|
|
658
|
+
const s = this.state.getSnapshot();
|
|
659
|
+
if (s.toast === seq) this.state.set({
|
|
660
|
+
...s,
|
|
661
|
+
toast: 0
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
/** Scope-teardown disposer: drop the draft and the toast, orphan in-flight work. */
|
|
665
|
+
dispose() {
|
|
666
|
+
this.generation += 1;
|
|
667
|
+
this.state.set({
|
|
668
|
+
...CLOSED,
|
|
669
|
+
toast: 0
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
//#endregion
|
|
674
|
+
//#region lib/types/client/surface.js
|
|
675
|
+
/**
|
|
676
|
+
* One Session's feedback surface: the message-feedback object layer and the
|
|
677
|
+
* dialog controller, plus the routing between them. A message target puts a
|
|
678
|
+
* negative judgment through the message controller; the Session target
|
|
679
|
+
* records through the `sessionFeedback` Remote.
|
|
680
|
+
* @module @deepseek-ai/dsh-client-ui-message-feedback/client/surface
|
|
681
|
+
*/
|
|
682
|
+
/** The per-session pair behind every entry of one Session. */
|
|
683
|
+
var FeedbackSurface = class {
|
|
684
|
+
ctx;
|
|
685
|
+
sessionId;
|
|
686
|
+
/** The Session's message-feedback object layer, shared by every message control. */
|
|
687
|
+
feedback;
|
|
688
|
+
/** The Session's dialog and toast state, shared by the overlay entry and the message controls. */
|
|
689
|
+
dialog;
|
|
690
|
+
/**
|
|
691
|
+
* @param ctx - the browser plugin context carrying both feedback Remotes.
|
|
692
|
+
* @param sessionId - Session owning the transcript and the remark.
|
|
693
|
+
*/
|
|
694
|
+
constructor(ctx, sessionId) {
|
|
695
|
+
this.ctx = ctx;
|
|
696
|
+
this.sessionId = sessionId;
|
|
697
|
+
this.feedback = new MessageFeedbackController(ctx, sessionId);
|
|
698
|
+
this.dialog = new FeedbackDialogController((target, entry) => target.kind === "message" ? this.feedback.rate(target.messageId, "negative", entry) : this.recordSession(entry));
|
|
699
|
+
}
|
|
700
|
+
/** Record one Session-level remark through the sessionFeedback Remote. */
|
|
701
|
+
async recordSession(entry) {
|
|
702
|
+
const carried = await this.ctx.remote.sessionFeedback.record({
|
|
703
|
+
sessionId: this.sessionId,
|
|
704
|
+
...entry
|
|
705
|
+
});
|
|
706
|
+
if (!carried.ok) return {
|
|
707
|
+
ok: false,
|
|
708
|
+
error: {
|
|
709
|
+
code: carried.error.code,
|
|
710
|
+
message: carried.error.message
|
|
711
|
+
}
|
|
467
712
|
};
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
713
|
+
if (carried.value.ok) return { ok: true };
|
|
714
|
+
return {
|
|
715
|
+
ok: false,
|
|
716
|
+
error: {
|
|
717
|
+
code: carried.value.error.code,
|
|
718
|
+
message: describe(carried.value.error.code)
|
|
719
|
+
}
|
|
473
720
|
};
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
(
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (wasOpen.current) triggerRef.current?.focus();
|
|
482
|
-
wasOpen.current = false;
|
|
483
|
-
}, [noteOpen]);
|
|
484
|
-
const likeLabel = rating === "positive" ? t("action.likeActive") : t("action.like");
|
|
485
|
-
const dislikeLabel = rating === "negative" ? t("action.dislikeActive") : t("action.dislike");
|
|
486
|
-
return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
487
|
-
(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
488
|
-
label: likeLabel,
|
|
489
|
-
side: "bottom",
|
|
490
|
-
children: (0, react_jsx_runtime.jsx)("button", {
|
|
491
|
-
type: "button",
|
|
492
|
-
className: MessageFeedbackActions_module_css_default.action,
|
|
493
|
-
"aria-label": likeLabel,
|
|
494
|
-
"aria-pressed": rating === "positive",
|
|
495
|
-
"data-active": rating === "positive" || void 0,
|
|
496
|
-
disabled: pending,
|
|
497
|
-
onFocus: seed,
|
|
498
|
-
onPointerEnter: seed,
|
|
499
|
-
onClick: () => {
|
|
500
|
-
onRate("positive");
|
|
501
|
-
},
|
|
502
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLikeOutline16, {})
|
|
503
|
-
})
|
|
504
|
-
}),
|
|
505
|
-
(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
|
|
506
|
-
label: dislikeLabel,
|
|
507
|
-
side: "bottom",
|
|
508
|
-
children: (0, react_jsx_runtime.jsx)("button", {
|
|
509
|
-
type: "button",
|
|
510
|
-
className: MessageFeedbackActions_module_css_default.action,
|
|
511
|
-
"aria-label": dislikeLabel,
|
|
512
|
-
"aria-pressed": rating === "negative",
|
|
513
|
-
"data-active": rating === "negative" || void 0,
|
|
514
|
-
disabled: pending,
|
|
515
|
-
onFocus: seed,
|
|
516
|
-
onPointerEnter: seed,
|
|
517
|
-
onClick: () => {
|
|
518
|
-
onRate("negative");
|
|
519
|
-
},
|
|
520
|
-
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDislikeOutline16, {})
|
|
521
|
-
})
|
|
522
|
-
}),
|
|
523
|
-
rating !== void 0 && (0, react_jsx_runtime.jsx)("button", {
|
|
524
|
-
ref: triggerRef,
|
|
525
|
-
type: "button",
|
|
526
|
-
className: MessageFeedbackActions_module_css_default.noteOpen,
|
|
527
|
-
"aria-haspopup": "dialog",
|
|
528
|
-
"aria-expanded": noteOpen,
|
|
529
|
-
onClick: toggleNote,
|
|
530
|
-
children: item?.note === void 0 ? t("note.open") : item.note
|
|
531
|
-
}),
|
|
532
|
-
rowFailure === null && loadFailed && (0, react_jsx_runtime.jsx)("span", {
|
|
533
|
-
className: MessageFeedbackActions_module_css_default.failure,
|
|
534
|
-
role: "status",
|
|
535
|
-
children: t("error.load")
|
|
536
|
-
}),
|
|
537
|
-
rowFailure !== null && (0, react_jsx_runtime.jsx)("span", {
|
|
538
|
-
className: MessageFeedbackActions_module_css_default.failure,
|
|
539
|
-
role: "status",
|
|
540
|
-
children: rowFailure
|
|
541
|
-
}),
|
|
542
|
-
!(rating !== void 0 && noteOpen) && noteFailure !== null && (0, react_jsx_runtime.jsx)("span", {
|
|
543
|
-
className: MessageFeedbackActions_module_css_default.failure,
|
|
544
|
-
role: "status",
|
|
545
|
-
children: noteFailure
|
|
546
|
-
}),
|
|
547
|
-
rating !== void 0 && noteOpen && (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)("div", {
|
|
548
|
-
ref: panelRef,
|
|
549
|
-
className: MessageFeedbackActions_module_css_default.notePanel,
|
|
550
|
-
role: "dialog",
|
|
551
|
-
"aria-label": t("note.dialog"),
|
|
552
|
-
style: pos ?? MEASURE_STYLE,
|
|
553
|
-
children: [
|
|
554
|
-
(0, react_jsx_runtime.jsx)("textarea", {
|
|
555
|
-
ref: inputRef,
|
|
556
|
-
className: MessageFeedbackActions_module_css_default.noteInput,
|
|
557
|
-
"aria-label": t("note.aria"),
|
|
558
|
-
placeholder: t("note.placeholder"),
|
|
559
|
-
value: draft,
|
|
560
|
-
rows: 3,
|
|
561
|
-
onChange: (event) => {
|
|
562
|
-
setDraft(event.target.value);
|
|
563
|
-
}
|
|
564
|
-
}),
|
|
565
|
-
(0, react_jsx_runtime.jsxs)("div", {
|
|
566
|
-
className: MessageFeedbackActions_module_css_default.noteActions,
|
|
567
|
-
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
568
|
-
type: "button",
|
|
569
|
-
className: MessageFeedbackActions_module_css_default.noteSave,
|
|
570
|
-
disabled: pending,
|
|
571
|
-
onClick: () => {
|
|
572
|
-
onSaveNote(rating);
|
|
573
|
-
},
|
|
574
|
-
children: t("note.save")
|
|
575
|
-
}), (0, react_jsx_runtime.jsx)("button", {
|
|
576
|
-
type: "button",
|
|
577
|
-
className: MessageFeedbackActions_module_css_default.noteCancel,
|
|
578
|
-
onClick: closeNote,
|
|
579
|
-
children: t("note.cancel")
|
|
580
|
-
})]
|
|
581
|
-
}),
|
|
582
|
-
noteFailure !== null && (0, react_jsx_runtime.jsx)("span", {
|
|
583
|
-
className: MessageFeedbackActions_module_css_default.failure,
|
|
584
|
-
role: "status",
|
|
585
|
-
children: noteFailure
|
|
586
|
-
})
|
|
587
|
-
]
|
|
588
|
-
}), document.body)
|
|
589
|
-
] });
|
|
590
|
-
}
|
|
721
|
+
}
|
|
722
|
+
/** Drop both controllers when the owning fiber unloads. */
|
|
723
|
+
dispose() {
|
|
724
|
+
this.feedback.dispose();
|
|
725
|
+
this.dialog.dispose();
|
|
726
|
+
}
|
|
727
|
+
};
|
|
591
728
|
//#endregion
|
|
592
729
|
//#region lib/types/client/locales.js
|
|
593
730
|
/** `feedback` namespace dictionaries. */
|
|
@@ -597,15 +734,22 @@ window.__ModuleLoader__.load({
|
|
|
597
734
|
"action.likeActive": "取消标记",
|
|
598
735
|
"action.dislike": "有问题的回答",
|
|
599
736
|
"action.dislikeActive": "取消标记",
|
|
600
|
-
"
|
|
601
|
-
"
|
|
602
|
-
"
|
|
603
|
-
"
|
|
604
|
-
"
|
|
605
|
-
"
|
|
737
|
+
"dialog.title": "提交反馈",
|
|
738
|
+
"dialog.categories": "反馈分类",
|
|
739
|
+
"dialog.detail": "反馈详情",
|
|
740
|
+
"dialog.hint": "填写详情以帮助我们改进体验,提交内容会包括当前对话的日志",
|
|
741
|
+
"category.task-result": "任务结果",
|
|
742
|
+
"category.instruction-following": "指令理解与遵循",
|
|
743
|
+
"category.product-interaction": "产品功能与交互",
|
|
744
|
+
"category.service-stability": "服务稳定性",
|
|
745
|
+
"category.resource-cost": "资源使用与费用",
|
|
746
|
+
"category.security-privacy-permission": "安全隐私与权限",
|
|
747
|
+
"category.other": "其他",
|
|
748
|
+
"toast.recorded": "感谢你的反馈",
|
|
606
749
|
"error.conflict": "这条反馈已在别处改动,已显示最新状态",
|
|
607
750
|
"error.load": "反馈状态加载失败",
|
|
608
|
-
"error.generic": "反馈保存失败"
|
|
751
|
+
"error.generic": "反馈保存失败",
|
|
752
|
+
"error.noteTooLarge": "描述太长,请缩短后再提交"
|
|
609
753
|
};
|
|
610
754
|
/** English dictionary, checked complete against the zh key set. */
|
|
611
755
|
const en = {
|
|
@@ -613,38 +757,46 @@ window.__ModuleLoader__.load({
|
|
|
613
757
|
"action.likeActive": "Remove rating",
|
|
614
758
|
"action.dislike": "Bad response",
|
|
615
759
|
"action.dislikeActive": "Remove rating",
|
|
616
|
-
"
|
|
617
|
-
"
|
|
618
|
-
"
|
|
619
|
-
"
|
|
620
|
-
"
|
|
621
|
-
"
|
|
760
|
+
"dialog.title": "Submit feedback",
|
|
761
|
+
"dialog.categories": "Feedback category",
|
|
762
|
+
"dialog.detail": "Feedback details",
|
|
763
|
+
"dialog.hint": "Add details to help us improve. Your submission will include the current conversation log.",
|
|
764
|
+
"category.task-result": "Task result",
|
|
765
|
+
"category.instruction-following": "Instruction understanding and following",
|
|
766
|
+
"category.product-interaction": "Product features and interaction",
|
|
767
|
+
"category.service-stability": "Service stability",
|
|
768
|
+
"category.resource-cost": "Resource usage and cost",
|
|
769
|
+
"category.security-privacy-permission": "Security, privacy, and permissions",
|
|
770
|
+
"category.other": "Other",
|
|
771
|
+
"toast.recorded": "Thanks for your feedback",
|
|
622
772
|
"error.conflict": "This feedback changed elsewhere; the latest state is shown",
|
|
623
773
|
"error.load": "Could not load feedback",
|
|
624
|
-
"error.generic": "Could not save feedback"
|
|
774
|
+
"error.generic": "Could not save feedback",
|
|
775
|
+
"error.noteTooLarge": "The description is too long; shorten it and submit again"
|
|
625
776
|
};
|
|
626
777
|
//#endregion
|
|
627
778
|
//#region lib/types/client/index.js
|
|
628
779
|
/**
|
|
629
|
-
*
|
|
630
|
-
* conversation.chat.assistant-actions strip
|
|
631
|
-
*
|
|
632
|
-
*
|
|
633
|
-
*
|
|
780
|
+
* Feedback surface plugin, browser half: the Like/Dislike entry in the
|
|
781
|
+
* conversation.chat.assistant-actions strip, the feedback dialog and its
|
|
782
|
+
* acknowledgement toast in conversation.input.overlay, and the `/feedback`
|
|
783
|
+
* decoration that opens the dialog from the composer menu or a bare typed
|
|
784
|
+
* command. One FeedbackSurface per Session backs every entry in that Session.
|
|
634
785
|
* @module @deepseek-ai/dsh-client-ui-message-feedback/client
|
|
635
786
|
*/
|
|
636
787
|
/** Dictionary namespace owned by this plugin. */
|
|
637
788
|
const NS = "feedback";
|
|
638
|
-
/** Required services: the slot registry, the Remote
|
|
789
|
+
/** Required services: the slot registry, the two Remote namespaces, and the copy. */
|
|
639
790
|
const inject = [
|
|
640
791
|
"slots",
|
|
641
792
|
"remote",
|
|
642
793
|
"remote.messageFeedback",
|
|
794
|
+
"remote.sessionFeedback",
|
|
643
795
|
"locale"
|
|
644
796
|
];
|
|
645
797
|
/**
|
|
646
|
-
* Client plugin body: the per-message feedback entry
|
|
647
|
-
*
|
|
798
|
+
* Client plugin body: the per-message feedback entry, the Session's dialog
|
|
799
|
+
* entry, the `/feedback` decoration, and their per-session surfaces.
|
|
648
800
|
* @param ctx - client root context.
|
|
649
801
|
*/
|
|
650
802
|
function apply(ctx) {
|
|
@@ -652,41 +804,79 @@ window.__ModuleLoader__.load({
|
|
|
652
804
|
zh,
|
|
653
805
|
en
|
|
654
806
|
}), "ui-message-feedback: dictionaries");
|
|
655
|
-
const
|
|
656
|
-
const
|
|
657
|
-
let
|
|
658
|
-
if (
|
|
659
|
-
|
|
660
|
-
|
|
807
|
+
const surfaces = /* @__PURE__ */ new Map();
|
|
808
|
+
const surfaceFor = (sessionId) => {
|
|
809
|
+
let surface = surfaces.get(sessionId);
|
|
810
|
+
if (surface === void 0) {
|
|
811
|
+
surface = new FeedbackSurface(ctx, sessionId);
|
|
812
|
+
surfaces.set(sessionId, surface);
|
|
661
813
|
}
|
|
662
|
-
return
|
|
814
|
+
return surface;
|
|
663
815
|
};
|
|
816
|
+
ctx.effect(() => () => {
|
|
817
|
+
for (const surface of surfaces.values()) surface.dispose();
|
|
818
|
+
surfaces.clear();
|
|
819
|
+
}, "ui-message-feedback: per-session surfaces");
|
|
664
820
|
ctx.on("connection/reset", () => {
|
|
665
|
-
for (const
|
|
821
|
+
for (const { feedback } of surfaces.values()) if (feedback.getSnapshot().status !== "cold") feedback.resync();
|
|
666
822
|
});
|
|
667
|
-
ctx.slots.inject("conversation.chat.assistant-actions", () => {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
823
|
+
ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register({
|
|
824
|
+
name: "conversation.chat.assistant-actions",
|
|
825
|
+
id: "feedback",
|
|
826
|
+
order: 10,
|
|
827
|
+
locale: NS,
|
|
828
|
+
inject: (sessionId) => {
|
|
829
|
+
const { feedback, dialog } = surfaceFor(sessionId);
|
|
830
|
+
return {
|
|
831
|
+
hooks: { feedback },
|
|
832
|
+
ensure: () => feedback.ensure(),
|
|
833
|
+
current: (messageId) => feedback.getSnapshot().items.get(messageId),
|
|
834
|
+
toggle: (messageId, rating) => feedback.toggle(messageId, rating),
|
|
835
|
+
openDialog: (messageId) => {
|
|
836
|
+
dialog.open({
|
|
837
|
+
kind: "message",
|
|
838
|
+
messageId
|
|
839
|
+
});
|
|
840
|
+
},
|
|
841
|
+
acknowledge: () => {
|
|
842
|
+
dialog.acknowledge();
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
}, MessageFeedbackActions));
|
|
847
|
+
ctx.slots.inject("conversation.input.overlay", () => ctx.slots.register({
|
|
848
|
+
name: "conversation.input.overlay",
|
|
849
|
+
id: "feedback-dialog",
|
|
850
|
+
order: 2,
|
|
851
|
+
locale: NS,
|
|
852
|
+
inject: (sessionId) => {
|
|
853
|
+
const { dialog } = surfaceFor(sessionId);
|
|
854
|
+
return {
|
|
855
|
+
hooks: { dialog: dialog.state },
|
|
856
|
+
edit: (draft) => {
|
|
857
|
+
dialog.edit(draft);
|
|
858
|
+
},
|
|
859
|
+
submit: () => dialog.submitDraft(),
|
|
860
|
+
dismiss: () => {
|
|
861
|
+
dialog.dismiss();
|
|
862
|
+
},
|
|
863
|
+
dismissToast: (seq) => {
|
|
864
|
+
dialog.dismissToast(seq);
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
}, FeedbackDialog));
|
|
869
|
+
ctx.inject(["commandUi"], (scope) => {
|
|
870
|
+
scope.effect(() => scope.commandUi.decorate({
|
|
871
|
+
name: "feedback",
|
|
872
|
+
available: () => true,
|
|
873
|
+
ui: {
|
|
874
|
+
kind: "action",
|
|
875
|
+
run: (session) => {
|
|
876
|
+
surfaceFor(session.sessionId).dialog.open({ kind: "session" });
|
|
877
|
+
}
|
|
683
878
|
}
|
|
684
|
-
},
|
|
685
|
-
return () => {
|
|
686
|
-
dispose();
|
|
687
|
-
for (const controller of controllers.values()) controller.dispose();
|
|
688
|
-
controllers.clear();
|
|
689
|
-
};
|
|
879
|
+
}), "ui-message-feedback: /feedback decoration");
|
|
690
880
|
});
|
|
691
881
|
}
|
|
692
882
|
//#endregion
|