dsh-plugin-choice-refresh 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/cordis.patch.yml +7 -0
- package/lib/client.js +852 -0
- package/lib/index.js +31 -0
- package/package.json +61 -0
- package/scripts/selfcheck.mjs +132 -0
- package/scripts/smoke-client.mjs +151 -0
- package//350/256/276/350/256/241/350/257/264/346/230/216.md +93 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,852 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: 'dsh-plugin-choice-refresh',
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
7
|
+
var react_jsx_runtime = require('react/jsx-runtime');
|
|
8
|
+
var react = require('react');
|
|
9
|
+
var jsx = react_jsx_runtime.jsx;
|
|
10
|
+
var jsxs = react_jsx_runtime.jsxs;
|
|
11
|
+
var Fragment = react_jsx_runtime.Fragment;
|
|
12
|
+
|
|
13
|
+
// ------------------------------------------------------------------
|
|
14
|
+
// 样式:一次注入 <style>,类名 dsr-*,全部走主题 CSS 变量。
|
|
15
|
+
// ------------------------------------------------------------------
|
|
16
|
+
var CSS = [
|
|
17
|
+
'.dsr-frame{padding:14px calc(var(--dsh-composer-side-clearance, 16px) + 16px) 12px;justify-content:center;display:flex}',
|
|
18
|
+
'.dsr-card{width:100%;max-width:var(--dsh-chat-content-width, 748px);border:1px solid var(--dsw-alias-border-l2-darkmode-thin);background:var(--dsw-specific-input-major);max-height:min(70vh,620px);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:20px;flex-direction:column;padding:0 0 10px;display:flex;overflow:hidden}',
|
|
19
|
+
'.dsr-card,.dsr-card *{box-sizing:border-box}',
|
|
20
|
+
'.dsr-header{flex-shrink:0;justify-content:space-between;align-items:flex-start;gap:16px;padding:20px 16px 0 24px;display:flex}',
|
|
21
|
+
'.dsr-headingBlock{min-width:0}',
|
|
22
|
+
'.dsr-eyebrow{color:var(--dsw-alias-label-tertiary);margin-bottom:5px;font-size:11px;line-height:16px}',
|
|
23
|
+
'.dsr-title{margin:0;font-size:16px;font-weight:500;line-height:22px}',
|
|
24
|
+
'.dsr-detail{margin:0 2px 8px 24px;white-space:pre-wrap;word-break:break-word;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}',
|
|
25
|
+
'.dsr-iconButton{width:24px;height:24px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:999px;place-items:center;padding:0;display:grid}',
|
|
26
|
+
'.dsr-iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
|
|
27
|
+
'.dsr-iconButton:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}',
|
|
28
|
+
'.dsr-body{overscroll-behavior:contain;flex-direction:column;flex:auto;min-height:0;display:flex;overflow-y:auto}',
|
|
29
|
+
'.dsr-grid{flex-direction:column;gap:1px;margin:8px 0 0;padding:4px 24px;display:flex}',
|
|
30
|
+
'.dsr-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(128px,1fr));gap:10px;padding:10px 24px 4px}',
|
|
31
|
+
'.dsr-cardBtn{position:relative;min-width:0;color:inherit;text-align:left;cursor:pointer;background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2-darkmode-thin);border-radius:14px;padding:0;overflow:hidden;display:flex;flex-direction:column;transition:border-color .12s ease,background .12s ease}',
|
|
32
|
+
'.dsr-cardBtn:hover:not(:disabled){border-color:var(--dsw-alias-interactive-bg-active);background:var(--dsw-alias-interactive-bg-hover)}',
|
|
33
|
+
'.dsr-cardBtn:disabled{cursor:default;opacity:.6}',
|
|
34
|
+
'.dsr-cardBtn.dsr-selected{border-color:var(--dsw-alias-interactive-bg-active);background:var(--dsw-alias-interactive-bg-hover);outline:2px solid var(--dsw-alias-interactive-bg-active);outline-offset:0}',
|
|
35
|
+
'.dsr-thumb{position:relative;width:100%;aspect-ratio:1/1;background:var(--dsw-alias-interactive-bg-hover);border:none;padding:0;overflow:hidden;display:flex;align-items:center;justify-content:center;cursor:zoom-in}',
|
|
36
|
+
'.dsr-thumb img{width:100%;height:100%;object-fit:cover;display:block}',
|
|
37
|
+
'.dsr-thumb .dsr-thumbFallback{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:16px;padding:8px;text-align:center;overflow:hidden}',
|
|
38
|
+
'.dsr-zoomHint{position:absolute;top:6px;left:6px;width:22px;height:22px;border-radius:999px;background:rgba(8,10,18,.55);color:#fff;place-items:center;display:grid;pointer-events:none;opacity:0;transition:opacity .12s ease;z-index:1}',
|
|
39
|
+
'.dsr-thumb:hover .dsr-zoomHint,.dsr-thumb:focus-visible .dsr-zoomHint{opacity:1}',
|
|
40
|
+
'.dsr-cardBtn:focus-visible{outline:2px solid var(--dsw-alias-interactive-bg-active);outline-offset:2px}',
|
|
41
|
+
'.dsr-check{position:absolute;top:6px;right:6px;width:20px;height:20px;border-radius:999px;background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary-inverted);place-items:center;display:none;font-size:12px;line-height:1;z-index:1}',
|
|
42
|
+
'.dsr-cardBtn.dsr-selected .dsr-check{display:grid}',
|
|
43
|
+
'.dsr-cardCopy{padding:8px 10px 10px;min-width:0}',
|
|
44
|
+
'.dsr-optionLabel{font-size:13px;font-weight:500;line-height:18px;overflow-wrap:anywhere}',
|
|
45
|
+
'.dsr-badge{display:inline-block;margin-left:6px;padding:1px 6px;border-radius:999px;background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-primary);font-size:10px;line-height:14px;vertical-align:1px}',
|
|
46
|
+
'.dsr-description{margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:15px;overflow-wrap:anywhere}',
|
|
47
|
+
'.dsr-option{width:100%;min-height:40px;color:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:10px;align-items:center;gap:10px;padding:8px 12px;display:flex}',
|
|
48
|
+
'.dsr-option:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}',
|
|
49
|
+
'.dsr-option:disabled{cursor:default}',
|
|
50
|
+
'.dsr-option.dsr-optionSelected{background:var(--dsw-alias-interactive-bg-hover)}',
|
|
51
|
+
'.dsr-number{width:24px;height:24px;flex-shrink:0;color:var(--dsw-alias-label-tertiary);border:1px solid var(--dsw-alias-border-l2-darkmode-thin);border-radius:999px;place-items:center;display:grid;font-size:12px;line-height:1}',
|
|
52
|
+
'.dsr-checkbox{width:18px;height:18px;flex-shrink:0;border:1px solid var(--dsw-alias-border-l2);border-radius:5px;place-items:center;display:grid}',
|
|
53
|
+
'.dsr-checkboxChecked{background:var(--dsw-alias-interactive-bg-active);border-color:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary-inverted)}',
|
|
54
|
+
'.dsr-optionCopy{flex:auto;min-width:0}',
|
|
55
|
+
'.dsr-optionLine{flex-direction:column;display:flex}',
|
|
56
|
+
'.dsr-customRow{width:100%;min-height:40px;align-items:center;gap:10px;padding:8px 12px;display:flex}',
|
|
57
|
+
'.dsr-customInput{flex:auto;min-width:0;color:inherit;background:0 0;border:none;outline:none;font-size:14px;line-height:22px}',
|
|
58
|
+
'.dsr-customInput::placeholder{color:var(--dsw-alias-label-tertiary)}',
|
|
59
|
+
'.dsr-customTextarea{width:100%;margin:8px 0 0;resize:none;color:inherit;background:var(--dsw-alias-interactive-bg-hover);border:1px solid var(--dsw-alias-border-l2-darkmode-thin);border-radius:10px;outline:none;padding:8px 10px;font-size:14px;line-height:22px;font-family:inherit}',
|
|
60
|
+
'.dsr-customTextarea:focus{border-color:var(--dsw-alias-interactive-bg-active)}',
|
|
61
|
+
'.dsr-footer{flex-shrink:0;flex-direction:column;gap:8px;padding:8px 16px 0;display:flex}',
|
|
62
|
+
'.dsr-footerRow{justify-content:space-between;align-items:center;gap:12px;display:flex}',
|
|
63
|
+
'.dsr-pager{flex-shrink:0;align-items:center;gap:6px;display:flex}',
|
|
64
|
+
'.dsr-progress{color:var(--dsw-alias-label-secondary);white-space:nowrap;word-spacing:-2px;padding:0 4px;font-size:14px;font-weight:500;line-height:24px}',
|
|
65
|
+
'.dsr-feedback{min-height:16px;color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:16px;flex:auto;padding:0 8px}',
|
|
66
|
+
'.dsr-actions{flex-shrink:0;align-items:center;gap:8px;display:flex;flex-wrap:wrap}',
|
|
67
|
+
'.dsr-btn{appearance:none;cursor:pointer;border-radius:999px;border:1px solid transparent;font-size:13px;line-height:20px;padding:5px 14px;font-family:inherit;white-space:nowrap}',
|
|
68
|
+
'.dsr-btn:disabled{cursor:default;opacity:.55}',
|
|
69
|
+
'.dsr-btn-outline{background:0 0;border-color:var(--dsw-alias-border-l2-darkmode-thin);color:var(--dsw-alias-label-primary)}',
|
|
70
|
+
'.dsr-btn-outline:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}',
|
|
71
|
+
'.dsr-btn-ghost{background:0 0;border-color:transparent;color:var(--dsw-alias-label-secondary)}',
|
|
72
|
+
'.dsr-btn-ghost:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
|
|
73
|
+
'.dsr-btn-primary{background:var(--dsw-alias-interactive-bg-active);color:var(--dsw-alias-label-primary-inverted)}',
|
|
74
|
+
'.dsr-btn-primary:hover:not(:disabled){filter:brightness(1.08)}',
|
|
75
|
+
'.dsr-lightbox{position:fixed;inset:0;z-index:9999;background:rgba(8,10,18,.86);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:52px 24px 28px;cursor:zoom-out}',
|
|
76
|
+
'.dsr-lightboxClose{position:absolute;top:16px;right:18px;width:34px;height:34px;border:1px solid var(--dsw-alias-border-l2-darkmode-thin);border-radius:999px;background:rgba(255,255,255,.08);color:#fff;cursor:pointer;place-items:center;display:grid;font-size:16px;line-height:1;padding:0}',
|
|
77
|
+
'.dsr-lightboxClose:hover{background:rgba(255,255,255,.18)}',
|
|
78
|
+
'.dsr-lightboxFigure{margin:0;max-width:min(92vw,1100px);display:flex;flex-direction:column;align-items:center;gap:10px}',
|
|
79
|
+
'.dsr-lightboxFigure img{max-width:100%;max-height:82vh;object-fit:contain;border-radius:8px;background:#fff;box-shadow:0 8px 40px rgba(0,0,0,.5)}',
|
|
80
|
+
'.dsr-lightboxCap{color:var(--dsw-alias-label-primary);font-size:13px;line-height:18px;text-align:center;overflow-wrap:anywhere}',
|
|
81
|
+
'.dsr-lightboxCap small{display:block;color:var(--dsw-alias-label-secondary);font-size:11px;line-height:15px;margin-top:2px}',
|
|
82
|
+
'@media (width<=720px){.dsr-card{border-radius:16px}.dsr-cards{grid-template-columns:repeat(auto-fill,minmax(104px,1fr))}.dsr-lightbox{padding:44px 12px 20px}}'
|
|
83
|
+
].join('');
|
|
84
|
+
var tagId = 'dsh-plugin-choice-refresh/choice.module.css';
|
|
85
|
+
if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css=' + JSON.stringify(tagId) + ']') === null) {
|
|
86
|
+
var tag = document.createElement('style');
|
|
87
|
+
tag.dataset.plugin = 'dsh-plugin-choice-refresh';
|
|
88
|
+
tag.dataset.pluginCss = tagId;
|
|
89
|
+
tag.textContent = CSS;
|
|
90
|
+
document.head.appendChild(tag);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ------------------------------------------------------------------
|
|
94
|
+
// 常量与纯函数(导出供 selfcheck / smoke 测试)
|
|
95
|
+
// ------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
/** image-tools 的图片标记前缀/后缀(与其服务端 buildPickMarker 互通)。 */
|
|
98
|
+
var MARKER_PREFIX = '<!--dsh-pick:v1:';
|
|
99
|
+
var MARKER_SUFFIX = '-->';
|
|
100
|
+
/** image-tools 的图片字节路由前缀(选择卡图片 <img src>)。 */
|
|
101
|
+
var IMAGE_ROUTE_PREFIX = '/dsh-plugin-image-tools/';
|
|
102
|
+
|
|
103
|
+
function decodeBase64Url(value) {
|
|
104
|
+
var b64 = value.replace(/-/g, '+').replace(/_/g, '/');
|
|
105
|
+
while (b64.length % 4 !== 0) b64 += '=';
|
|
106
|
+
var binary = atob(b64);
|
|
107
|
+
var bytes = new Uint8Array(binary.length);
|
|
108
|
+
for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
109
|
+
return new TextDecoder('utf-8').decode(bytes);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** 解析 image-tools 写入 detail 的图片标记;无标记返回 null。 */
|
|
113
|
+
function parseMarker(detail) {
|
|
114
|
+
if (typeof detail !== 'string' || !detail.startsWith(MARKER_PREFIX)) return null;
|
|
115
|
+
var end = detail.indexOf(MARKER_SUFFIX, MARKER_PREFIX.length);
|
|
116
|
+
if (end < 0) return null;
|
|
117
|
+
try {
|
|
118
|
+
var data = JSON.parse(decodeBase64Url(detail.slice(MARKER_PREFIX.length, end)));
|
|
119
|
+
if (data === null || typeof data !== 'object' || typeof data.pickId !== 'string') return null;
|
|
120
|
+
var images = Array.isArray(data.images) ? data.images.filter(function (n) { return Number.isInteger(n) && n >= 0; }) : [];
|
|
121
|
+
return { pickId: data.pickId, images: images, human: detail.slice(end + MARKER_SUFFIX.length) };
|
|
122
|
+
} catch (e) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** 该批问题是否为 plan-review 意图(原生 PlanReviewPanel 的领地,本插件不认领)。 */
|
|
128
|
+
function isPlanReviewBatch(questions) {
|
|
129
|
+
if (!Array.isArray(questions) || questions.length !== 1) return false;
|
|
130
|
+
var question = questions[0];
|
|
131
|
+
return question !== null && typeof question === 'object' && question.intent !== void 0 && question.intent !== null && question.intent.kind === 'plan-review';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** 去掉 label 末尾的推荐标注(展示用;答案值保持原 label)。 */
|
|
135
|
+
function parseRecommendedLabel(label) {
|
|
136
|
+
var suffix = /\s*(?:\((?:recommended|推荐)\)|((?:recommended|推荐)))\s*$/i;
|
|
137
|
+
return suffix.test(label)
|
|
138
|
+
? { label: label.replace(suffix, ''), recommended: true }
|
|
139
|
+
: { label: label, recommended: false };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** 输入法组合中不触发回车提交。 */
|
|
143
|
+
function isComposing(event) {
|
|
144
|
+
return event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 页面语言是否中文(决定注入给模型的提示语语言)。 */
|
|
148
|
+
function uiIsChinese() {
|
|
149
|
+
return typeof document !== 'undefined' && (document.documentElement.lang || '').toLowerCase().indexOf('zh') === 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 构造注入给模型的「重新生成 / 更多选项」提示语。
|
|
154
|
+
* @param {string} kind - 'refresh' | 'more'。
|
|
155
|
+
* @param {object[]} questions - 当前整批问题(含当前题)。
|
|
156
|
+
* @param {number} currentIndex - 当前题下标。
|
|
157
|
+
* @param {boolean} [chinese] - 中文还是英文提示(缺省按页面语言)。
|
|
158
|
+
* @returns {string} 注入消息文本。
|
|
159
|
+
*/
|
|
160
|
+
function buildSteerText(kind, questions, currentIndex, chinese) {
|
|
161
|
+
var useZh = chinese === void 0 ? uiIsChinese() : Boolean(chinese);
|
|
162
|
+
var list = Array.isArray(questions) ? questions : [];
|
|
163
|
+
var batchNote = list.length > 1
|
|
164
|
+
? (useZh
|
|
165
|
+
? '(该批共 ' + list.length + ' 道题,其余题目也请一并重新询问。)'
|
|
166
|
+
: ' (The batch had ' + list.length + ' questions; please re-ask the rest as well.)')
|
|
167
|
+
: '';
|
|
168
|
+
if (useZh) {
|
|
169
|
+
if (kind === 'more') {
|
|
170
|
+
return '【系统 · 更多选项】用户觉得选项太少,请保留原选项补充到 6~10 个后,立即重新调用 ask_user_question(纯文字)或 ask_user_choice(可含图片)提问(推荐项放第一位并标注 "(推荐)")。' + batchNote;
|
|
171
|
+
}
|
|
172
|
+
return '【系统 · 选项刷新】用户对刚才的选项不满意,请换一批全新选项(5~8 个,换角度/风格/维度避免雷同)后,立即重新调用 ask_user_question(纯文字)或 ask_user_choice(可含图片)提问(推荐项放第一位并标注 "(推荐)")。' + batchNote;
|
|
173
|
+
}
|
|
174
|
+
if (kind === 'more') {
|
|
175
|
+
return '[System · More options] The user wants more options: keep the originals, add more to reach 6~10, then immediately re-ask via ask_user_question (text-only) or ask_user_choice (may include images), keeping the recommended option first with "(Recommended)".' + batchNote;
|
|
176
|
+
}
|
|
177
|
+
return '[System · Regenerate options] The user is unhappy with the previous options: provide a fresh set (5~8, a different angle/style/dimension), then immediately re-ask via ask_user_question (text-only) or ask_user_choice (may include images), keeping the recommended option first with "(Recommended)".' + batchNote;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ------------------------------------------------------------------
|
|
181
|
+
// 待答载体(与服务端回答协议一致)
|
|
182
|
+
// ------------------------------------------------------------------
|
|
183
|
+
var PendingChoice = class {
|
|
184
|
+
constructor(wait) { this.wait = wait; }
|
|
185
|
+
get key() { return this.wait.key; }
|
|
186
|
+
get sessionId() { return this.wait.sessionId; }
|
|
187
|
+
get questions() { return this.wait.payload.questions; }
|
|
188
|
+
async answer(answer) {
|
|
189
|
+
var receipt = await this.wait.respond({ ok: true, value: { sessionId: this.wait.sessionId, answer: answer } });
|
|
190
|
+
if (!receipt.accepted) throw new Error('question response rejected: ' + receipt.reason);
|
|
191
|
+
}
|
|
192
|
+
async cancel() {
|
|
193
|
+
var receipt = await this.wait.respond({
|
|
194
|
+
ok: false,
|
|
195
|
+
error: { code: 'cancelled', message: 'the user closed this question request', details: {} }
|
|
196
|
+
});
|
|
197
|
+
if (!receipt.accepted) throw new Error('question cancellation rejected: ' + receipt.reason);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// ------------------------------------------------------------------
|
|
202
|
+
// 子组件:图片卡片 / 文字选项 / 放大查看
|
|
203
|
+
// ------------------------------------------------------------------
|
|
204
|
+
var ZOOM_ICON_SVG = '<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.5" y2="16.5"/><line x1="8" y1="11" x2="14" y2="11"/></svg>';
|
|
205
|
+
|
|
206
|
+
function recommendedBadge(display, t) {
|
|
207
|
+
return display.recommended ? jsx('span', { className: 'dsr-badge', children: t('option.recommended') }) : null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function ImageCardBtn(props) {
|
|
211
|
+
var pickId = props.pickId;
|
|
212
|
+
var option = props.option;
|
|
213
|
+
var optionIndex = props.optionIndex;
|
|
214
|
+
var selected = props.selected;
|
|
215
|
+
var multi = props.multi;
|
|
216
|
+
var disabled = props.disabled;
|
|
217
|
+
var t = props.t;
|
|
218
|
+
var display = parseRecommendedLabel(option.label);
|
|
219
|
+
var failedState = react.useState(false);
|
|
220
|
+
var failed = failedState[0];
|
|
221
|
+
var setFailed = failedState[1];
|
|
222
|
+
var src = IMAGE_ROUTE_PREFIX + pickId + '/' + optionIndex;
|
|
223
|
+
var onSelect = function () {
|
|
224
|
+
if (disabled) return;
|
|
225
|
+
props.onSelect();
|
|
226
|
+
};
|
|
227
|
+
return jsxs('div', {
|
|
228
|
+
className: 'dsr-cardBtn' + (selected ? ' dsr-selected' : ''),
|
|
229
|
+
role: multi ? 'checkbox' : 'radio',
|
|
230
|
+
'aria-checked': selected,
|
|
231
|
+
'aria-label': display.label,
|
|
232
|
+
'aria-disabled': disabled,
|
|
233
|
+
tabIndex: 0,
|
|
234
|
+
onClick: onSelect,
|
|
235
|
+
onKeyDown: function (event) {
|
|
236
|
+
if (disabled || event.target !== event.currentTarget) return;
|
|
237
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
238
|
+
event.preventDefault();
|
|
239
|
+
props.onSelect();
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
children: [
|
|
243
|
+
jsxs('button', {
|
|
244
|
+
type: 'button',
|
|
245
|
+
className: 'dsr-thumb',
|
|
246
|
+
'aria-label': failed ? t('image.failed') : t('image.zoom') + ':' + display.label,
|
|
247
|
+
title: t('image.zoom'),
|
|
248
|
+
disabled: disabled,
|
|
249
|
+
onClick: function (event) {
|
|
250
|
+
event.stopPropagation();
|
|
251
|
+
if (!failed) props.onZoom();
|
|
252
|
+
},
|
|
253
|
+
children: [
|
|
254
|
+
failed
|
|
255
|
+
? jsx('div', { className: 'dsr-thumbFallback', children: t('image.failed') })
|
|
256
|
+
: jsx('img', {
|
|
257
|
+
src: src,
|
|
258
|
+
alt: display.label,
|
|
259
|
+
loading: 'lazy',
|
|
260
|
+
onError: function () { setFailed(true); }
|
|
261
|
+
}),
|
|
262
|
+
jsx('span', { className: 'dsr-zoomHint', 'aria-hidden': 'true', dangerouslySetInnerHTML: { __html: ZOOM_ICON_SVG } }),
|
|
263
|
+
selected && jsx('span', { className: 'dsr-check', 'aria-hidden': 'true', children: '\u2713' })
|
|
264
|
+
]
|
|
265
|
+
}),
|
|
266
|
+
jsxs('div', {
|
|
267
|
+
className: 'dsr-cardCopy',
|
|
268
|
+
children: [
|
|
269
|
+
jsxs('div', { className: 'dsr-optionLabel', children: [display.label, recommendedBadge(display, t)] }),
|
|
270
|
+
option.description !== void 0 && jsx('div', { className: 'dsr-description', children: option.description })
|
|
271
|
+
]
|
|
272
|
+
})
|
|
273
|
+
]
|
|
274
|
+
}, optionIndex);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function TextOptionRow(props) {
|
|
278
|
+
var option = props.option;
|
|
279
|
+
var optionIndex = props.optionIndex;
|
|
280
|
+
var selected = props.selected;
|
|
281
|
+
var multi = props.multi;
|
|
282
|
+
var disabled = props.disabled;
|
|
283
|
+
var t = props.t;
|
|
284
|
+
var display = parseRecommendedLabel(option.label);
|
|
285
|
+
return jsxs('button', {
|
|
286
|
+
type: 'button',
|
|
287
|
+
className: 'dsr-option' + (selected && !multi ? ' dsr-optionSelected' : ''),
|
|
288
|
+
role: multi ? 'checkbox' : 'radio',
|
|
289
|
+
'aria-checked': selected,
|
|
290
|
+
'aria-label': display.label,
|
|
291
|
+
disabled: disabled,
|
|
292
|
+
onClick: function () { props.onClick(); },
|
|
293
|
+
onKeyDown: props.onKeyDown,
|
|
294
|
+
children: [
|
|
295
|
+
multi
|
|
296
|
+
? jsx('span', {
|
|
297
|
+
className: 'dsr-checkbox' + (selected ? ' dsr-checkboxChecked' : ''),
|
|
298
|
+
'aria-hidden': 'true',
|
|
299
|
+
children: selected ? '\u2713' : null
|
|
300
|
+
})
|
|
301
|
+
: jsx('span', { className: 'dsr-number', children: optionIndex + 1 }),
|
|
302
|
+
jsxs('span', {
|
|
303
|
+
className: 'dsr-optionCopy',
|
|
304
|
+
children: [
|
|
305
|
+
jsxs('span', {
|
|
306
|
+
className: 'dsr-optionLine',
|
|
307
|
+
children: [
|
|
308
|
+
jsxs('span', { className: 'dsr-optionLabel', children: [display.label, recommendedBadge(display, t)] }),
|
|
309
|
+
option.description !== void 0 && jsx('span', { className: 'dsr-description', children: option.description })
|
|
310
|
+
]
|
|
311
|
+
})
|
|
312
|
+
]
|
|
313
|
+
})
|
|
314
|
+
]
|
|
315
|
+
}, optionIndex);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function Lightbox(props) {
|
|
319
|
+
var zoom = props.zoom;
|
|
320
|
+
var onClose = props.onClose;
|
|
321
|
+
var t = props.t;
|
|
322
|
+
var closeRef = react.useRef(null);
|
|
323
|
+
react.useEffect(function () {
|
|
324
|
+
if (zoom === null) return;
|
|
325
|
+
var previous = document.body.style.overflow;
|
|
326
|
+
document.body.style.overflow = 'hidden';
|
|
327
|
+
var focusTarget = closeRef.current;
|
|
328
|
+
if (focusTarget !== null && typeof focusTarget.focus === 'function') focusTarget.focus();
|
|
329
|
+
function onKeyDown(event) {
|
|
330
|
+
if (event.key === 'Escape') onClose();
|
|
331
|
+
}
|
|
332
|
+
document.addEventListener('keydown', onKeyDown);
|
|
333
|
+
return function () {
|
|
334
|
+
document.body.style.overflow = previous;
|
|
335
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
336
|
+
};
|
|
337
|
+
}, [zoom, onClose]);
|
|
338
|
+
if (zoom === null) return null;
|
|
339
|
+
return jsxs('div', {
|
|
340
|
+
className: 'dsr-lightbox',
|
|
341
|
+
role: 'dialog',
|
|
342
|
+
'aria-modal': 'true',
|
|
343
|
+
'aria-label': zoom.label,
|
|
344
|
+
onClick: onClose,
|
|
345
|
+
children: [
|
|
346
|
+
jsx('button', {
|
|
347
|
+
type: 'button',
|
|
348
|
+
ref: closeRef,
|
|
349
|
+
className: 'dsr-lightboxClose',
|
|
350
|
+
'aria-label': t('image.close'),
|
|
351
|
+
title: t('image.close'),
|
|
352
|
+
onClick: function (event) { event.stopPropagation(); onClose(); },
|
|
353
|
+
children: '\u2715'
|
|
354
|
+
}),
|
|
355
|
+
jsxs('figure', {
|
|
356
|
+
className: 'dsr-lightboxFigure',
|
|
357
|
+
onClick: function (event) { event.stopPropagation(); },
|
|
358
|
+
children: [
|
|
359
|
+
jsx('img', { src: zoom.src, alt: zoom.label }),
|
|
360
|
+
jsxs('figcaption', {
|
|
361
|
+
className: 'dsr-lightboxCap',
|
|
362
|
+
children: [
|
|
363
|
+
zoom.label,
|
|
364
|
+
zoom.description !== void 0 && jsx('small', { children: zoom.description })
|
|
365
|
+
]
|
|
366
|
+
})
|
|
367
|
+
]
|
|
368
|
+
})
|
|
369
|
+
]
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ------------------------------------------------------------------
|
|
374
|
+
// 主流程:分页多题、图文混排、多选、自定义、跳过、刷新、更多选项
|
|
375
|
+
// ------------------------------------------------------------------
|
|
376
|
+
function ChoiceFlow(props) {
|
|
377
|
+
var pending = props.pending;
|
|
378
|
+
var ctx = props.ctx;
|
|
379
|
+
var t = props.t;
|
|
380
|
+
var questions = pending.questions;
|
|
381
|
+
var markers = questions.map(function (q) { return parseMarker(q.detail); });
|
|
382
|
+
var indexState = react.useState(0);
|
|
383
|
+
var index = indexState[0];
|
|
384
|
+
var setIndex = indexState[1];
|
|
385
|
+
var draftsState = react.useState(function () {
|
|
386
|
+
return questions.map(function () { return { selected: [], custom: '', skipped: false }; });
|
|
387
|
+
});
|
|
388
|
+
var drafts = draftsState[0];
|
|
389
|
+
var setDrafts = draftsState[1];
|
|
390
|
+
var busyState = react.useState(null);
|
|
391
|
+
var busy = busyState[0];
|
|
392
|
+
var setBusy = busyState[1];
|
|
393
|
+
var errorState = react.useState(null);
|
|
394
|
+
var error = errorState[0];
|
|
395
|
+
var setError = errorState[1];
|
|
396
|
+
var zoomState = react.useState(null);
|
|
397
|
+
var zoom = zoomState[0];
|
|
398
|
+
var setZoom = zoomState[1];
|
|
399
|
+
var closeZoom = react.useCallback(function () { setZoom(null); }, []);
|
|
400
|
+
|
|
401
|
+
var question = questions[index];
|
|
402
|
+
var marker = markers[index];
|
|
403
|
+
var draft = drafts[index];
|
|
404
|
+
var hasOptions = question.options !== void 0 && question.options !== null && question.options.length > 0;
|
|
405
|
+
var hasImages = marker !== null && marker.images.length > 0;
|
|
406
|
+
|
|
407
|
+
var cancelFlow = function () {
|
|
408
|
+
setBusy('cancel');
|
|
409
|
+
setError(null);
|
|
410
|
+
pending.cancel().catch(function (cause) {
|
|
411
|
+
setBusy(null);
|
|
412
|
+
setError({ text: cause instanceof Error ? cause.message : String(cause) });
|
|
413
|
+
});
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
/** 把一条用户指令注入当前会话(steer:打断当前回合,模型随即重新提问)。 */
|
|
417
|
+
var steerSession = function (text) {
|
|
418
|
+
var sessionId = pending.sessionId;
|
|
419
|
+
var binding = ctx.sessions !== void 0 && typeof ctx.sessions.binding === 'function' ? ctx.sessions.binding(sessionId) : void 0;
|
|
420
|
+
if (binding !== void 0 && binding.session !== void 0 && typeof binding.session.prompt === 'function') {
|
|
421
|
+
return binding.session.prompt([{ type: 'text', text: text }], 'steer');
|
|
422
|
+
}
|
|
423
|
+
if (ctx.remote !== void 0 && ctx.remote.sessions !== void 0 && typeof ctx.remote.sessions.prompt === 'function') {
|
|
424
|
+
return ctx.remote.sessions.prompt({ sessionId: sessionId, mode: 'steer', content: [{ type: 'text', text: text }] });
|
|
425
|
+
}
|
|
426
|
+
throw new Error('choice-refresh: no session prompt transport available');
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
/** 刷新 / 更多选项:先解除当前问题,再 steer 提示模型重新提问。 */
|
|
430
|
+
var requestRegenerate = function (kind) {
|
|
431
|
+
setBusy(kind);
|
|
432
|
+
setError(null);
|
|
433
|
+
pending.cancel()
|
|
434
|
+
.then(function () {
|
|
435
|
+
return steerSession(buildSteerText(kind, questions, index));
|
|
436
|
+
})
|
|
437
|
+
.catch(function (cause) {
|
|
438
|
+
setBusy(null);
|
|
439
|
+
setError({ text: cause instanceof Error ? cause.message : String(cause) });
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
var updateDraft = function (update) {
|
|
444
|
+
setDrafts(function (current) {
|
|
445
|
+
return current.map(function (item, itemIndex) { return itemIndex === index ? update(item) : item; });
|
|
446
|
+
});
|
|
447
|
+
setError(null);
|
|
448
|
+
};
|
|
449
|
+
var choose = function (label) {
|
|
450
|
+
updateDraft(function (current) {
|
|
451
|
+
if (question.multiSelect === true) {
|
|
452
|
+
var selected = current.selected.indexOf(label) >= 0
|
|
453
|
+
? current.selected.filter(function (item) { return item !== label; })
|
|
454
|
+
: current.selected.concat([label]);
|
|
455
|
+
return { selected: selected, custom: '', skipped: false };
|
|
456
|
+
}
|
|
457
|
+
return { selected: [label], custom: '', skipped: false };
|
|
458
|
+
});
|
|
459
|
+
if (question.multiSelect !== true && index < questions.length - 1) setIndex(index + 1);
|
|
460
|
+
};
|
|
461
|
+
var answered = function (item) { return item.selected.length > 0 || item.custom.trim() !== ''; };
|
|
462
|
+
var completed = function (item) { return answered(item) || item.skipped; };
|
|
463
|
+
var submitDrafts = function (values) {
|
|
464
|
+
var missing = values.findIndex(function (item) { return !completed(item); });
|
|
465
|
+
if (missing >= 0) {
|
|
466
|
+
setIndex(missing);
|
|
467
|
+
setError({ key: 'error.incomplete' });
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
var answer = {
|
|
471
|
+
answers: questions.map(function (item, itemIndex) {
|
|
472
|
+
var value = values[itemIndex];
|
|
473
|
+
if (value.skipped) return { id: item.id, selected: [] };
|
|
474
|
+
var custom = value.custom.trim();
|
|
475
|
+
return {
|
|
476
|
+
id: item.id,
|
|
477
|
+
selected: custom === '' || item.multiSelect === true ? value.selected : [],
|
|
478
|
+
...(custom === '' ? {} : { custom: custom })
|
|
479
|
+
};
|
|
480
|
+
})
|
|
481
|
+
};
|
|
482
|
+
setBusy('answer');
|
|
483
|
+
setError(null);
|
|
484
|
+
pending.answer(answer).catch(function (cause) {
|
|
485
|
+
setBusy(null);
|
|
486
|
+
setError({ text: cause instanceof Error ? cause.message : String(cause) });
|
|
487
|
+
});
|
|
488
|
+
};
|
|
489
|
+
var continueFlow = function () {
|
|
490
|
+
if (!answered(draft)) {
|
|
491
|
+
setError({ key: 'error.unanswered' });
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (index < questions.length - 1) {
|
|
495
|
+
setIndex(index + 1);
|
|
496
|
+
setError(null);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
submitDrafts(drafts);
|
|
500
|
+
};
|
|
501
|
+
var draftCustom = function (event) {
|
|
502
|
+
var value = event.target.value;
|
|
503
|
+
updateDraft(function (current) {
|
|
504
|
+
return {
|
|
505
|
+
selected: question.multiSelect === true ? current.selected : [],
|
|
506
|
+
custom: value,
|
|
507
|
+
skipped: false
|
|
508
|
+
};
|
|
509
|
+
});
|
|
510
|
+
};
|
|
511
|
+
var continueFromCustom = function (event) {
|
|
512
|
+
if (event.key !== 'Enter' || event.shiftKey || isComposing(event)) return;
|
|
513
|
+
event.preventDefault();
|
|
514
|
+
continueFlow();
|
|
515
|
+
};
|
|
516
|
+
var skipQuestion = function () {
|
|
517
|
+
var nextDrafts = drafts.map(function (item, itemIndex) {
|
|
518
|
+
return itemIndex === index ? { selected: [], custom: '', skipped: true } : item;
|
|
519
|
+
});
|
|
520
|
+
setDrafts(nextDrafts);
|
|
521
|
+
setError(null);
|
|
522
|
+
if (index < questions.length - 1) {
|
|
523
|
+
setIndex(index + 1);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
submitDrafts(nextDrafts);
|
|
527
|
+
};
|
|
528
|
+
var onKeyDownOption = function (event) {
|
|
529
|
+
if (event.key !== 'Enter' || !drafts.every(completed)) return;
|
|
530
|
+
event.preventDefault();
|
|
531
|
+
submitDrafts(drafts);
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
var detailText = marker !== null ? marker.human : (typeof question.detail === 'string' ? question.detail : '');
|
|
535
|
+
var options = question.options !== void 0 && question.options !== null ? question.options : [];
|
|
536
|
+
var imageIndexSet = marker !== null ? marker.images : [];
|
|
537
|
+
var isImageOption = function (optionIndex) { return imageIndexSet.indexOf(optionIndex) >= 0; };
|
|
538
|
+
|
|
539
|
+
return jsx('div', {
|
|
540
|
+
className: 'dsr-frame',
|
|
541
|
+
'data-question-key': pending.key,
|
|
542
|
+
children: [
|
|
543
|
+
jsxs('section', {
|
|
544
|
+
className: 'dsr-card',
|
|
545
|
+
'aria-labelledby': 'dsr-q-' + pending.key + '-' + index,
|
|
546
|
+
children: [
|
|
547
|
+
jsxs('header', {
|
|
548
|
+
className: 'dsr-header',
|
|
549
|
+
children: [
|
|
550
|
+
jsxs('div', {
|
|
551
|
+
className: 'dsr-headingBlock',
|
|
552
|
+
children: [
|
|
553
|
+
question.header !== void 0 && jsx('div', { className: 'dsr-eyebrow', children: question.header }),
|
|
554
|
+
jsx('h2', { className: 'dsr-title', id: 'dsr-q-' + pending.key + '-' + index, children: question.question })
|
|
555
|
+
]
|
|
556
|
+
}),
|
|
557
|
+
jsx('button', {
|
|
558
|
+
type: 'button',
|
|
559
|
+
className: 'dsr-iconButton',
|
|
560
|
+
'aria-label': t('nav.cancel'),
|
|
561
|
+
title: t('nav.cancel'),
|
|
562
|
+
disabled: busy !== null,
|
|
563
|
+
onClick: cancelFlow,
|
|
564
|
+
children: '\u2715'
|
|
565
|
+
})
|
|
566
|
+
]
|
|
567
|
+
}),
|
|
568
|
+
jsxs('div', {
|
|
569
|
+
className: 'dsr-body',
|
|
570
|
+
children: [
|
|
571
|
+
detailText !== '' && jsx('p', { className: 'dsr-detail', children: detailText }),
|
|
572
|
+
hasImages
|
|
573
|
+
? jsxs('div', {
|
|
574
|
+
className: 'dsr-cards',
|
|
575
|
+
role: question.multiSelect === true ? 'group' : 'radiogroup',
|
|
576
|
+
children: options.map(function (option, optionIndex) {
|
|
577
|
+
if (!isImageOption(optionIndex)) return null;
|
|
578
|
+
var selected = draft.selected.indexOf(option.label) >= 0;
|
|
579
|
+
return jsx(ImageCardBtn, {
|
|
580
|
+
pickId: marker.pickId,
|
|
581
|
+
option: option,
|
|
582
|
+
optionIndex: optionIndex,
|
|
583
|
+
selected: selected,
|
|
584
|
+
multi: question.multiSelect === true,
|
|
585
|
+
disabled: busy !== null,
|
|
586
|
+
t: t,
|
|
587
|
+
onSelect: function () { choose(option.label); },
|
|
588
|
+
onZoom: function () {
|
|
589
|
+
setZoom({
|
|
590
|
+
src: IMAGE_ROUTE_PREFIX + marker.pickId + '/' + optionIndex,
|
|
591
|
+
label: option.label,
|
|
592
|
+
description: option.description
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
}, optionIndex);
|
|
596
|
+
})
|
|
597
|
+
})
|
|
598
|
+
: null,
|
|
599
|
+
jsxs('div', {
|
|
600
|
+
className: 'dsr-grid',
|
|
601
|
+
role: question.multiSelect === true ? 'group' : 'radiogroup',
|
|
602
|
+
children: [
|
|
603
|
+
options.map(function (option, optionIndex) {
|
|
604
|
+
if (hasImages && isImageOption(optionIndex)) return null;
|
|
605
|
+
var selected = draft.selected.indexOf(option.label) >= 0;
|
|
606
|
+
return jsx(TextOptionRow, {
|
|
607
|
+
option: option,
|
|
608
|
+
optionIndex: optionIndex,
|
|
609
|
+
selected: selected,
|
|
610
|
+
multi: question.multiSelect === true,
|
|
611
|
+
disabled: busy !== null,
|
|
612
|
+
t: t,
|
|
613
|
+
onClick: function () { choose(option.label); },
|
|
614
|
+
onKeyDown: onKeyDownOption
|
|
615
|
+
}, optionIndex);
|
|
616
|
+
}),
|
|
617
|
+
hasOptions
|
|
618
|
+
? jsxs('div', {
|
|
619
|
+
className: 'dsr-customRow',
|
|
620
|
+
children: [
|
|
621
|
+
multiInput(draft, question, t, busy, draftCustom, continueFromCustom)
|
|
622
|
+
]
|
|
623
|
+
})
|
|
624
|
+
: jsx('textarea', {
|
|
625
|
+
autoFocus: true,
|
|
626
|
+
className: 'dsr-customTextarea',
|
|
627
|
+
value: draft.custom,
|
|
628
|
+
disabled: busy !== null,
|
|
629
|
+
rows: 2,
|
|
630
|
+
placeholder: t('custom.placeholder'),
|
|
631
|
+
onChange: draftCustom,
|
|
632
|
+
onKeyDown: continueFromCustom
|
|
633
|
+
})
|
|
634
|
+
]
|
|
635
|
+
})
|
|
636
|
+
]
|
|
637
|
+
}),
|
|
638
|
+
jsxs('footer', {
|
|
639
|
+
className: 'dsr-footer',
|
|
640
|
+
children: [
|
|
641
|
+
jsxs('div', {
|
|
642
|
+
className: 'dsr-footerRow',
|
|
643
|
+
children: [
|
|
644
|
+
jsxs('div', {
|
|
645
|
+
className: 'dsr-pager',
|
|
646
|
+
children: [
|
|
647
|
+
jsx('button', {
|
|
648
|
+
type: 'button',
|
|
649
|
+
className: 'dsr-iconButton',
|
|
650
|
+
'aria-label': t('nav.prev'),
|
|
651
|
+
disabled: index === 0 || busy !== null,
|
|
652
|
+
onClick: function () { setIndex(index - 1); setError(null); },
|
|
653
|
+
children: '\u2039'
|
|
654
|
+
}),
|
|
655
|
+
jsxs('span', { className: 'dsr-progress', children: [index + 1, ' / ', questions.length] }),
|
|
656
|
+
jsx('button', {
|
|
657
|
+
type: 'button',
|
|
658
|
+
className: 'dsr-iconButton',
|
|
659
|
+
'aria-label': t('nav.next'),
|
|
660
|
+
disabled: index === questions.length - 1 || busy !== null,
|
|
661
|
+
onClick: function () { setIndex(index + 1); setError(null); },
|
|
662
|
+
children: '\u203a'
|
|
663
|
+
})
|
|
664
|
+
]
|
|
665
|
+
}),
|
|
666
|
+
jsx('div', {
|
|
667
|
+
className: 'dsr-feedback',
|
|
668
|
+
role: 'status',
|
|
669
|
+
children: error === null ? null : ('key' in error ? t(error.key) : error.text)
|
|
670
|
+
})
|
|
671
|
+
]
|
|
672
|
+
}),
|
|
673
|
+
jsxs('div', {
|
|
674
|
+
className: 'dsr-footerRow',
|
|
675
|
+
children: [
|
|
676
|
+
jsxs('div', {
|
|
677
|
+
className: 'dsr-actions',
|
|
678
|
+
children: [
|
|
679
|
+
hasOptions && jsx('button', {
|
|
680
|
+
type: 'button',
|
|
681
|
+
className: 'dsr-btn dsr-btn-ghost',
|
|
682
|
+
disabled: busy !== null,
|
|
683
|
+
onClick: function () { requestRegenerate('refresh'); },
|
|
684
|
+
children: busy === 'refresh' ? t('busy.refresh') : t('refresh.button')
|
|
685
|
+
}),
|
|
686
|
+
hasOptions && jsx('button', {
|
|
687
|
+
type: 'button',
|
|
688
|
+
className: 'dsr-btn dsr-btn-ghost',
|
|
689
|
+
disabled: busy !== null,
|
|
690
|
+
onClick: function () { requestRegenerate('more'); },
|
|
691
|
+
children: busy === 'more' ? t('busy.more') : t('more.button')
|
|
692
|
+
})
|
|
693
|
+
]
|
|
694
|
+
}),
|
|
695
|
+
jsxs('div', {
|
|
696
|
+
className: 'dsr-actions',
|
|
697
|
+
children: [
|
|
698
|
+
jsx('button', {
|
|
699
|
+
type: 'button',
|
|
700
|
+
className: 'dsr-btn dsr-btn-outline',
|
|
701
|
+
disabled: busy !== null,
|
|
702
|
+
onClick: skipQuestion,
|
|
703
|
+
children: t('action.skip')
|
|
704
|
+
}),
|
|
705
|
+
jsx('button', {
|
|
706
|
+
type: 'button',
|
|
707
|
+
className: 'dsr-btn dsr-btn-primary',
|
|
708
|
+
disabled: busy !== null || !answered(draft),
|
|
709
|
+
onClick: continueFlow,
|
|
710
|
+
children: busy === 'answer' ? t('submitting') : (index === questions.length - 1 ? t('submit') : t('action.next'))
|
|
711
|
+
})
|
|
712
|
+
]
|
|
713
|
+
})
|
|
714
|
+
]
|
|
715
|
+
})
|
|
716
|
+
]
|
|
717
|
+
})
|
|
718
|
+
]
|
|
719
|
+
}, 'dsr-card'),
|
|
720
|
+
zoom !== null && jsx(Lightbox, { zoom: zoom, onClose: closeZoom, t: t })
|
|
721
|
+
]
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/** 多选/单选自定义答案行的前置控件。 */
|
|
726
|
+
function multiInput(draft, question, t, busy, draftCustom, continueFromCustom) {
|
|
727
|
+
return jsxs(Fragment, {
|
|
728
|
+
children: [
|
|
729
|
+
question.multiSelect === true
|
|
730
|
+
? jsx('span', {
|
|
731
|
+
className: 'dsr-checkbox' + (draft.custom !== '' ? ' dsr-checkboxChecked' : ''),
|
|
732
|
+
'aria-hidden': 'true',
|
|
733
|
+
children: draft.custom !== '' ? '\u2713' : null
|
|
734
|
+
})
|
|
735
|
+
: jsx('span', { className: 'dsr-number', 'aria-hidden': 'true', children: '\u270E' }),
|
|
736
|
+
jsx('input', {
|
|
737
|
+
type: 'text',
|
|
738
|
+
className: 'dsr-customInput',
|
|
739
|
+
value: draft.custom,
|
|
740
|
+
disabled: busy !== null,
|
|
741
|
+
placeholder: t('custom.placeholder'),
|
|
742
|
+
onChange: draftCustom,
|
|
743
|
+
onKeyDown: continueFromCustom
|
|
744
|
+
})
|
|
745
|
+
]
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// ------------------------------------------------------------------
|
|
750
|
+
// 组件入口 + 链条目选择器
|
|
751
|
+
// ------------------------------------------------------------------
|
|
752
|
+
function ChoiceComposer(props) {
|
|
753
|
+
var pending = react.useMemo(function () { return new PendingChoice(props.matched); }, [props.matched]);
|
|
754
|
+
return jsx(ChoiceFlow, { pending: pending, ctx: props.ctx, t: props.t }, pending.key);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* 认领所有「非 plan-review」的 question 交互(priority -300,先于
|
|
759
|
+
* image-tools 的 -100 与原生 UI 的 0)。plan-review 意图放行给原生
|
|
760
|
+
* PlanReviewPanel;纯文字 / 带图片标记的问题都归本插件渲染。
|
|
761
|
+
*/
|
|
762
|
+
function selectChoice(_ref) {
|
|
763
|
+
var interactions = _ref.interactions;
|
|
764
|
+
for (var i = 0; i < interactions.length; i++) {
|
|
765
|
+
var item = interactions[i];
|
|
766
|
+
if (item.kind !== 'question') continue;
|
|
767
|
+
var payload = item.payload;
|
|
768
|
+
var questions = payload !== null && typeof payload === 'object' && Array.isArray(payload.questions) ? payload.questions : [];
|
|
769
|
+
if (isPlanReviewBatch(questions)) continue;
|
|
770
|
+
return item;
|
|
771
|
+
}
|
|
772
|
+
return null;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// ------------------------------------------------------------------
|
|
776
|
+
// 文案
|
|
777
|
+
// ------------------------------------------------------------------
|
|
778
|
+
var NS = 'choice-refresh';
|
|
779
|
+
var zh = {
|
|
780
|
+
'error.incomplete': '请先完成这道问题。',
|
|
781
|
+
'error.unanswered': '请选择一个选项或填写自定义答案。',
|
|
782
|
+
'nav.prev': '上一题',
|
|
783
|
+
'nav.next': '下一题',
|
|
784
|
+
'nav.cancel': '放弃整组问题',
|
|
785
|
+
'option.recommended': '推荐',
|
|
786
|
+
'custom.placeholder': '输入你的答案',
|
|
787
|
+
'action.skip': '跳过本题',
|
|
788
|
+
'action.next': '下一题',
|
|
789
|
+
'submit': '提交',
|
|
790
|
+
'submitting': '提交中…',
|
|
791
|
+
'refresh.button': '重新生成选项',
|
|
792
|
+
'more.button': '更多选项',
|
|
793
|
+
'busy.refresh': '重新生成中…',
|
|
794
|
+
'busy.more': '补充更多选项中…',
|
|
795
|
+
'image.failed': '图片加载失败',
|
|
796
|
+
'image.zoom': '放大查看',
|
|
797
|
+
'image.close': '关闭'
|
|
798
|
+
};
|
|
799
|
+
var en = {
|
|
800
|
+
'error.incomplete': 'Please complete this question first.',
|
|
801
|
+
'error.unanswered': 'Please select an option or enter a custom answer.',
|
|
802
|
+
'nav.prev': 'Previous question',
|
|
803
|
+
'nav.next': 'Next question',
|
|
804
|
+
'nav.cancel': 'Dismiss all questions',
|
|
805
|
+
'option.recommended': 'Recommended',
|
|
806
|
+
'custom.placeholder': 'Type your answer',
|
|
807
|
+
'action.skip': 'Skip this question',
|
|
808
|
+
'action.next': 'Next',
|
|
809
|
+
'submit': 'Submit',
|
|
810
|
+
'submitting': 'Submitting…',
|
|
811
|
+
'refresh.button': 'Regenerate options',
|
|
812
|
+
'more.button': 'More options',
|
|
813
|
+
'busy.refresh': 'Regenerating…',
|
|
814
|
+
'busy.more': 'Adding more…',
|
|
815
|
+
'image.failed': 'Image failed to load',
|
|
816
|
+
'image.zoom': 'Zoom in',
|
|
817
|
+
'image.close': 'Close'
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
function apply(ctx) {
|
|
821
|
+
ctx.effect(function () {
|
|
822
|
+
return ctx.locale.register(NS, { zh: zh, en: en });
|
|
823
|
+
}, 'dsh-plugin-choice-refresh: dictionaries');
|
|
824
|
+
ctx.slots.inject('conversation.composer', function () {
|
|
825
|
+
return ctx.slots.register({
|
|
826
|
+
name: 'conversation.composer',
|
|
827
|
+
select: selectChoice,
|
|
828
|
+
locale: NS,
|
|
829
|
+
priority: -300,
|
|
830
|
+
// 把插件自己的 ctx(sessions/remote)注入组件,供 steer 提示使用。
|
|
831
|
+
inject: function () { return { ctx: ctx }; }
|
|
832
|
+
}, ChoiceComposer);
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
exports.apply = apply;
|
|
837
|
+
exports.inject = ['slots', 'locale', 'sessions', 'remote'];
|
|
838
|
+
// 供冒烟测试/复用:纯函数、选择器、载体与组件。
|
|
839
|
+
exports.parseMarker = parseMarker;
|
|
840
|
+
exports.isPlanReviewBatch = isPlanReviewBatch;
|
|
841
|
+
exports.buildSteerText = buildSteerText;
|
|
842
|
+
exports.parseRecommendedLabel = parseRecommendedLabel;
|
|
843
|
+
exports.selectChoice = selectChoice;
|
|
844
|
+
exports.PendingChoice = PendingChoice;
|
|
845
|
+
exports.ChoiceComposer = ChoiceComposer;
|
|
846
|
+
exports.ChoiceFlow = ChoiceFlow;
|
|
847
|
+
exports.Lightbox = Lightbox;
|
|
848
|
+
exports.MARKER_PREFIX = MARKER_PREFIX;
|
|
849
|
+
exports.IMAGE_ROUTE_PREFIX = IMAGE_ROUTE_PREFIX;
|
|
850
|
+
return module.exports;
|
|
851
|
+
}
|
|
852
|
+
});
|