dsh-sidenote 0.3.4 → 0.3.5
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/lib/client.js +147 -100
- package/lib/types/client/annotate/send.d.ts +11 -0
- package/package.json +1 -1
- package/src/client/annotate/send.ts +66 -0
package/lib/client.js
CHANGED
|
@@ -2181,92 +2181,92 @@ window.__ModuleLoader__.load({
|
|
|
2181
2181
|
document.head.appendChild(tag);
|
|
2182
2182
|
}
|
|
2183
2183
|
var sidechat_module_css_default = {
|
|
2184
|
-
"
|
|
2185
|
-
"
|
|
2186
|
-
"
|
|
2187
|
-
"
|
|
2188
|
-
"empty": "_5KEEsG_empty",
|
|
2189
|
-
"dotRunning": "_5KEEsG_dotRunning",
|
|
2190
|
-
"toolBody": "_5KEEsG_toolBody",
|
|
2184
|
+
"todoBody": "_5KEEsG_todoBody",
|
|
2185
|
+
"attachButton": "_5KEEsG_attachButton",
|
|
2186
|
+
"writeNoticeClose": "_5KEEsG_writeNoticeClose",
|
|
2187
|
+
"pendingBtnGhost": "_5KEEsG_pendingBtnGhost",
|
|
2191
2188
|
"attachImg": "_5KEEsG_attachImg",
|
|
2192
|
-
"
|
|
2193
|
-
"
|
|
2194
|
-
"
|
|
2189
|
+
"composer": "_5KEEsG_composer",
|
|
2190
|
+
"headerSide": "_5KEEsG_headerSide",
|
|
2191
|
+
"toolGenericBody": "_5KEEsG_toolGenericBody",
|
|
2192
|
+
"toolHead": "_5KEEsG_toolHead",
|
|
2193
|
+
"assistantBody": "_5KEEsG_assistantBody",
|
|
2194
|
+
"sendButton": "_5KEEsG_sendButton",
|
|
2195
2195
|
"userBubble": "_5KEEsG_userBubble",
|
|
2196
2196
|
"rowPreview": "_5KEEsG_rowPreview",
|
|
2197
|
-
"
|
|
2198
|
-
"
|
|
2199
|
-
"toolGenericBody": "_5KEEsG_toolGenericBody",
|
|
2200
|
-
"jumpBottom": "_5KEEsG_jumpBottom",
|
|
2201
|
-
"streamingHint": "_5KEEsG_streamingHint",
|
|
2202
|
-
"pendingTitle": "_5KEEsG_pendingTitle",
|
|
2203
|
-
"inheritedBody": "_5KEEsG_inheritedBody",
|
|
2204
|
-
"pendingQHeader": "_5KEEsG_pendingQHeader",
|
|
2197
|
+
"empty": "_5KEEsG_empty",
|
|
2198
|
+
"densityToggle": "_5KEEsG_densityToggle",
|
|
2205
2199
|
"pendingOption": "_5KEEsG_pendingOption",
|
|
2206
|
-
"
|
|
2207
|
-
"
|
|
2208
|
-
"
|
|
2209
|
-
"
|
|
2210
|
-
"
|
|
2200
|
+
"emptyIcon": "_5KEEsG_emptyIcon",
|
|
2201
|
+
"errorRow": "_5KEEsG_errorRow",
|
|
2202
|
+
"todoDot": "_5KEEsG_todoDot",
|
|
2203
|
+
"pendingHead": "_5KEEsG_pendingHead",
|
|
2204
|
+
"flowRowError": "_5KEEsG_flowRowError",
|
|
2205
|
+
"pendingReason": "_5KEEsG_pendingReason",
|
|
2206
|
+
"pendingQuestion": "_5KEEsG_pendingQuestion",
|
|
2211
2207
|
"actionGroup": "_5KEEsG_actionGroup",
|
|
2212
|
-
"
|
|
2213
|
-
"toolBodyText": "_5KEEsG_toolBodyText",
|
|
2208
|
+
"jumpBottom": "_5KEEsG_jumpBottom",
|
|
2214
2209
|
"sidechat-pulse": "_5KEEsG_sidechat-pulse",
|
|
2215
|
-
"
|
|
2216
|
-
"
|
|
2217
|
-
"
|
|
2218
|
-
"
|
|
2219
|
-
"
|
|
2220
|
-
"
|
|
2221
|
-
"
|
|
2222
|
-
"
|
|
2223
|
-
"
|
|
2210
|
+
"pendingBar": "_5KEEsG_pendingBar",
|
|
2211
|
+
"userRow": "_5KEEsG_userRow",
|
|
2212
|
+
"transcript": "_5KEEsG_transcript",
|
|
2213
|
+
"assistantRow": "_5KEEsG_assistantRow",
|
|
2214
|
+
"flowRow": "_5KEEsG_flowRow",
|
|
2215
|
+
"pendingCustom": "_5KEEsG_pendingCustom",
|
|
2216
|
+
"pendingCard": "_5KEEsG_pendingCard",
|
|
2217
|
+
"dotPending": "_5KEEsG_dotPending",
|
|
2218
|
+
"reasoningBody": "_5KEEsG_reasoningBody",
|
|
2219
|
+
"todoText": "_5KEEsG_todoText",
|
|
2220
|
+
"slashItemName": "_5KEEsG_slashItemName",
|
|
2224
2221
|
"stopButton": "_5KEEsG_stopButton",
|
|
2225
|
-
"
|
|
2226
|
-
"slashMenu": "_5KEEsG_slashMenu",
|
|
2227
|
-
"writeNotice": "_5KEEsG_writeNotice",
|
|
2228
|
-
"input": "_5KEEsG_input",
|
|
2222
|
+
"slashItem": "_5KEEsG_slashItem",
|
|
2229
2223
|
"densityRow": "_5KEEsG_densityRow",
|
|
2230
|
-
"
|
|
2224
|
+
"root": "_5KEEsG_root",
|
|
2225
|
+
"slashMenu": "_5KEEsG_slashMenu",
|
|
2226
|
+
"stateScreen": "_5KEEsG_stateScreen",
|
|
2227
|
+
"pendingActions": "_5KEEsG_pendingActions",
|
|
2228
|
+
"rowActions": "_5KEEsG_rowActions",
|
|
2229
|
+
"pendingOptionDesc": "_5KEEsG_pendingOptionDesc",
|
|
2230
|
+
"streamingHint": "_5KEEsG_streamingHint",
|
|
2231
|
+
"attachRail": "_5KEEsG_attachRail",
|
|
2232
|
+
"pendingQText": "_5KEEsG_pendingQText",
|
|
2233
|
+
"dotIdle": "_5KEEsG_dotIdle",
|
|
2234
|
+
"slashItemActive": "_5KEEsG_slashItemActive",
|
|
2235
|
+
"toolBody": "_5KEEsG_toolBody",
|
|
2236
|
+
"toolError": "_5KEEsG_toolError",
|
|
2237
|
+
"toolRunning": "_5KEEsG_toolRunning",
|
|
2231
2238
|
"sentChip": "_5KEEsG_sentChip",
|
|
2232
|
-
"
|
|
2233
|
-
"
|
|
2234
|
-
"
|
|
2239
|
+
"pendingQHeader": "_5KEEsG_pendingQHeader",
|
|
2240
|
+
"body": "_5KEEsG_body",
|
|
2241
|
+
"emptyTitle": "_5KEEsG_emptyTitle",
|
|
2242
|
+
"toolBodyText": "_5KEEsG_toolBodyText",
|
|
2243
|
+
"pendingOptionActive": "_5KEEsG_pendingOptionActive",
|
|
2244
|
+
"inheritedBody": "_5KEEsG_inheritedBody",
|
|
2245
|
+
"dotRunning": "_5KEEsG_dotRunning",
|
|
2246
|
+
"reflowButton": "_5KEEsG_reflowButton",
|
|
2247
|
+
"composerChips": "_5KEEsG_composerChips",
|
|
2248
|
+
"pendingTitle": "_5KEEsG_pendingTitle",
|
|
2235
2249
|
"pendingBtnPrimary": "_5KEEsG_pendingBtnPrimary",
|
|
2236
|
-
"
|
|
2237
|
-
"assistantRow": "_5KEEsG_assistantRow",
|
|
2238
|
-
"flowRow": "_5KEEsG_flowRow",
|
|
2239
|
-
"userRow": "_5KEEsG_userRow",
|
|
2250
|
+
"composerCard": "_5KEEsG_composerCard",
|
|
2240
2251
|
"toolCard": "_5KEEsG_toolCard",
|
|
2241
|
-
"
|
|
2252
|
+
"reasoning": "_5KEEsG_reasoning",
|
|
2242
2253
|
"noticeRow": "_5KEEsG_noticeRow",
|
|
2254
|
+
"stateDetail": "_5KEEsG_stateDetail",
|
|
2243
2255
|
"slashItemDetail": "_5KEEsG_slashItemDetail",
|
|
2244
|
-
"transcript": "_5KEEsG_transcript",
|
|
2245
|
-
"toolRunning": "_5KEEsG_toolRunning",
|
|
2246
|
-
"emptyIcon": "_5KEEsG_emptyIcon",
|
|
2247
|
-
"pendingCustom": "_5KEEsG_pendingCustom",
|
|
2248
|
-
"headerSide": "_5KEEsG_headerSide",
|
|
2249
|
-
"parentStrip": "_5KEEsG_parentStrip",
|
|
2250
|
-
"modelSelect": "_5KEEsG_modelSelect",
|
|
2251
|
-
"pendingHead": "_5KEEsG_pendingHead",
|
|
2252
|
-
"root": "_5KEEsG_root",
|
|
2253
|
-
"flowRowError": "_5KEEsG_flowRowError",
|
|
2254
|
-
"writeNoticeClose": "_5KEEsG_writeNoticeClose",
|
|
2255
|
-
"slashGroupTitle": "_5KEEsG_slashGroupTitle",
|
|
2256
|
-
"sendButton": "_5KEEsG_sendButton",
|
|
2257
|
-
"slashItemName": "_5KEEsG_slashItemName",
|
|
2258
|
-
"body": "_5KEEsG_body",
|
|
2259
|
-
"emptyText": "_5KEEsG_emptyText",
|
|
2260
|
-
"composer": "_5KEEsG_composer",
|
|
2261
|
-
"emptyTitle": "_5KEEsG_emptyTitle",
|
|
2262
2256
|
"attachRemove": "_5KEEsG_attachRemove",
|
|
2263
|
-
"dotPending": "_5KEEsG_dotPending",
|
|
2264
|
-
"todoItem": "_5KEEsG_todoItem",
|
|
2265
|
-
"pendingQText": "_5KEEsG_pendingQText",
|
|
2266
|
-
"pendingOptionDesc": "_5KEEsG_pendingOptionDesc",
|
|
2267
2257
|
"pendingDesc": "_5KEEsG_pendingDesc",
|
|
2258
|
+
"sentChipRow": "_5KEEsG_sentChipRow",
|
|
2259
|
+
"writeNotice": "_5KEEsG_writeNotice",
|
|
2260
|
+
"attachThumb": "_5KEEsG_attachThumb",
|
|
2261
|
+
"modelSelect": "_5KEEsG_modelSelect",
|
|
2262
|
+
"slashGroupTitle": "_5KEEsG_slashGroupTitle",
|
|
2268
2263
|
"modelLabel": "_5KEEsG_modelLabel",
|
|
2269
|
-
"
|
|
2264
|
+
"emptyText": "_5KEEsG_emptyText",
|
|
2265
|
+
"parentStrip": "_5KEEsG_parentStrip",
|
|
2266
|
+
"input": "_5KEEsG_input",
|
|
2267
|
+
"composerFoot": "_5KEEsG_composerFoot",
|
|
2268
|
+
"toolLocations": "_5KEEsG_toolLocations",
|
|
2269
|
+
"todoItem": "_5KEEsG_todoItem"
|
|
2270
2270
|
};
|
|
2271
2271
|
//#endregion
|
|
2272
2272
|
//#region src/client/chat/ToolCard.tsx
|
|
@@ -4709,43 +4709,43 @@ window.__ModuleLoader__.load({
|
|
|
4709
4709
|
document.head.appendChild(tag);
|
|
4710
4710
|
}
|
|
4711
4711
|
var annotate_module_css_default = {
|
|
4712
|
-
"
|
|
4713
|
-
"
|
|
4714
|
-
"
|
|
4715
|
-
"
|
|
4716
|
-
"dshSidenoteHintIn": "_SVViG_dshSidenoteHintIn",
|
|
4717
|
-
"editorNew": "_SVViG_editorNew",
|
|
4718
|
-
"chipRow": "_SVViG_chipRow",
|
|
4719
|
-
"editorTextarea": "_SVViG_editorTextarea",
|
|
4712
|
+
"editorTarget": "_SVViG_editorTarget",
|
|
4713
|
+
"cancelButton": "_SVViG_cancelButton",
|
|
4714
|
+
"hintClose": "_SVViG_hintClose",
|
|
4715
|
+
"chipWrap": "_SVViG_chipWrap",
|
|
4720
4716
|
"editorAsk": "_SVViG_editorAsk",
|
|
4721
|
-
"
|
|
4722
|
-
"chip": "_SVViG_chip",
|
|
4723
|
-
"chipText": "_SVViG_chipText",
|
|
4724
|
-
"chipPanel": "_SVViG_chipPanel",
|
|
4717
|
+
"editorAskRow": "_SVViG_editorAskRow",
|
|
4725
4718
|
"editorError": "_SVViG_editorError",
|
|
4726
|
-
"toolbarBelow": "_SVViG_toolbarBelow",
|
|
4727
|
-
"sentCardQuote": "_SVViG_sentCardQuote",
|
|
4728
|
-
"hint": "_SVViG_hint",
|
|
4729
|
-
"sentChipRow": "_SVViG_sentChipRow",
|
|
4730
|
-
"deleteButton": "_SVViG_deleteButton",
|
|
4731
|
-
"sentCardNote": "_SVViG_sentCardNote",
|
|
4732
4719
|
"confirmButton": "_SVViG_confirmButton",
|
|
4733
|
-
"
|
|
4720
|
+
"toolbar": "_SVViG_toolbar",
|
|
4721
|
+
"editorInput": "_SVViG_editorInput",
|
|
4722
|
+
"toolbarButton": "_SVViG_toolbarButton",
|
|
4723
|
+
"chip": "_SVViG_chip",
|
|
4724
|
+
"chipNumber": "_SVViG_chipNumber",
|
|
4725
|
+
"sentCardQuote": "_SVViG_sentCardQuote",
|
|
4734
4726
|
"editorFooter": "_SVViG_editorFooter",
|
|
4727
|
+
"sentCardNote": "_SVViG_sentCardNote",
|
|
4728
|
+
"hint": "_SVViG_hint",
|
|
4735
4729
|
"sentCardTitle": "_SVViG_sentCardTitle",
|
|
4736
|
-
"
|
|
4737
|
-
"
|
|
4738
|
-
"
|
|
4739
|
-
"
|
|
4730
|
+
"chipPanel": "_SVViG_chipPanel",
|
|
4731
|
+
"sentChip": "_SVViG_sentChip",
|
|
4732
|
+
"dshSidenoteHintIn": "_SVViG_dshSidenoteHintIn",
|
|
4733
|
+
"editorTextarea": "_SVViG_editorTextarea",
|
|
4734
|
+
"editorEdit": "_SVViG_editorEdit",
|
|
4740
4735
|
"editorSpacer": "_SVViG_editorSpacer",
|
|
4741
|
-
"
|
|
4742
|
-
"
|
|
4736
|
+
"badgeSent": "_SVViG_badgeSent",
|
|
4737
|
+
"chipNote": "_SVViG_chipNote",
|
|
4738
|
+
"chipRow": "_SVViG_chipRow",
|
|
4739
|
+
"highlight": "_SVViG_highlight",
|
|
4743
4740
|
"saveButton": "_SVViG_saveButton",
|
|
4744
|
-
"
|
|
4745
|
-
"
|
|
4746
|
-
"
|
|
4747
|
-
"
|
|
4748
|
-
"
|
|
4741
|
+
"chipText": "_SVViG_chipText",
|
|
4742
|
+
"sentChipRow": "_SVViG_sentChipRow",
|
|
4743
|
+
"toolbarBelow": "_SVViG_toolbarBelow",
|
|
4744
|
+
"deleteButton": "_SVViG_deleteButton",
|
|
4745
|
+
"chipRemove": "_SVViG_chipRemove",
|
|
4746
|
+
"editorNew": "_SVViG_editorNew",
|
|
4747
|
+
"chipClearAll": "_SVViG_chipClearAll",
|
|
4748
|
+
"badge": "_SVViG_badge"
|
|
4749
4749
|
};
|
|
4750
4750
|
//#endregion
|
|
4751
4751
|
//#region src/client/annotate/hint.tsx
|
|
@@ -5584,6 +5584,23 @@ window.__ModuleLoader__.load({
|
|
|
5584
5584
|
function installSendInterceptor(ctx, store, reflow) {
|
|
5585
5585
|
/** 重入/连按护栏:事务进行中吞掉命中识别面的 Enter/点击(不重复驱动)。 */
|
|
5586
5586
|
let committing = false;
|
|
5587
|
+
/**
|
|
5588
|
+
* 手势尾随 click 的一次性吞并。pointerdown 提交后,同一次点击的 click 仍会
|
|
5589
|
+
* 到达,而此时宿主主按钮已按 `primaryStops = running && empty` 变身「停止」
|
|
5590
|
+
* ——放行它会立刻 `stop()` 掉刚发起的这一轮(表现:assistant/attempt 空流、
|
|
5591
|
+
* 缺 turn/end、且没有任何错误提示)。只吞这一发,并带兜底超时。
|
|
5592
|
+
*/
|
|
5593
|
+
let swallowNextClick = false;
|
|
5594
|
+
let swallowTimer = 0;
|
|
5595
|
+
const clearSwallow = () => {
|
|
5596
|
+
swallowNextClick = false;
|
|
5597
|
+
window.clearTimeout(swallowTimer);
|
|
5598
|
+
};
|
|
5599
|
+
const armSwallow = () => {
|
|
5600
|
+
swallowNextClick = true;
|
|
5601
|
+
window.clearTimeout(swallowTimer);
|
|
5602
|
+
swallowTimer = window.setTimeout(clearSwallow, 2e3);
|
|
5603
|
+
};
|
|
5587
5604
|
const currentSessionId = () => {
|
|
5588
5605
|
try {
|
|
5589
5606
|
return ctx.sessions.list.getSnapshot().current ?? "";
|
|
@@ -5663,6 +5680,12 @@ window.__ModuleLoader__.load({
|
|
|
5663
5680
|
const target = event.target;
|
|
5664
5681
|
if (!(target instanceof Element)) return;
|
|
5665
5682
|
if (target.closest("[data-composer-seat]") === null) return;
|
|
5683
|
+
if (swallowNextClick) {
|
|
5684
|
+
clearSwallow();
|
|
5685
|
+
event.preventDefault();
|
|
5686
|
+
event.stopImmediatePropagation();
|
|
5687
|
+
return;
|
|
5688
|
+
}
|
|
5666
5689
|
const button = target.closest("button");
|
|
5667
5690
|
if (button === null) return;
|
|
5668
5691
|
if (committing) {
|
|
@@ -5675,11 +5698,35 @@ window.__ModuleLoader__.load({
|
|
|
5675
5698
|
event.preventDefault();
|
|
5676
5699
|
event.stopImmediatePropagation();
|
|
5677
5700
|
};
|
|
5701
|
+
/**
|
|
5702
|
+
* 空草稿补位:只接管「因空草稿而 disabled」的主按钮。enabled 时立即放行,
|
|
5703
|
+
* 正常路径仍由上面的 click 拦截负责 —— 二者以 button.disabled 互斥。
|
|
5704
|
+
*/
|
|
5705
|
+
const onPointerDown = (event) => {
|
|
5706
|
+
if (event.button !== 0) return;
|
|
5707
|
+
const target = event.target;
|
|
5708
|
+
if (!(target instanceof Element)) return;
|
|
5709
|
+
const button = target.closest("button");
|
|
5710
|
+
if (!(button instanceof HTMLButtonElement)) return;
|
|
5711
|
+
if (!button.disabled) return;
|
|
5712
|
+
if (button !== findSendButtonInCard()) return;
|
|
5713
|
+
if (committing) return;
|
|
5714
|
+
const sessionId = currentSessionId();
|
|
5715
|
+
if (sessionId === "") return;
|
|
5716
|
+
const input = resolveInput(ctx, sessionId);
|
|
5717
|
+
if (input === void 0 || input.state.getSnapshot().draft.trim() !== "") return;
|
|
5718
|
+
if (!hijack()) return;
|
|
5719
|
+
armSwallow();
|
|
5720
|
+
event.preventDefault();
|
|
5721
|
+
event.stopImmediatePropagation();
|
|
5722
|
+
};
|
|
5678
5723
|
document.addEventListener("keydown", onKeyDown, true);
|
|
5679
5724
|
document.addEventListener("click", onClick, true);
|
|
5725
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
5680
5726
|
return () => {
|
|
5681
5727
|
document.removeEventListener("keydown", onKeyDown, true);
|
|
5682
5728
|
document.removeEventListener("click", onClick, true);
|
|
5729
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
5683
5730
|
};
|
|
5684
5731
|
}
|
|
5685
5732
|
//#endregion
|
|
@@ -19,6 +19,17 @@
|
|
|
19
19
|
* plain 且草稿清空 = 发出(markSent 只翻当时拼进去的那批 id,C2 P1-3);
|
|
20
20
|
* 回到 plain 而草稿未清 = 发送失败(宿主 notice + 留稿)→ 仅在草稿仍以
|
|
21
21
|
* 我们拼的前缀开头时剥离回滚(C2 P2-3);相位未到终态前绝不回滚。
|
|
22
|
+
*
|
|
23
|
+
* 空草稿补位:宿主主按钮在 `draft.trim()==="" && attachments.length===0` 时
|
|
24
|
+
* 被置为原生 `disabled`(dsh-client-ui-conversation 的 `empty`),而注释/回流
|
|
25
|
+
* 按设计不进草稿 —— 于是此时**没有任何可达的发送手势**:浏览器不为 disabled
|
|
26
|
+
* 的按钮派发 `click`(`mousedown` 同样不发),只有 `pointerdown` 仍会到达它
|
|
27
|
+
* (Chromium / Firefox / WebKit 实测一致)。因此对「空草稿导致 disabled 的
|
|
28
|
+
* 主按钮」补一条 pointerdown 拦截;enabled 时一切照旧走 click,两条路径以
|
|
29
|
+
* `button.disabled` 互斥,不会双驱动。**该手势尾随的 click 必须一并吞掉**:
|
|
30
|
+
* 提交瞬间草稿变空,宿主主按钮随即按 `primaryStops = running && empty` 变身
|
|
31
|
+
* 「停止」,放行那一发 click 会立刻 `stop()` 掉刚发起的这一轮 —— 症状是
|
|
32
|
+
* `assistant/attempt` 空流、缺 `turn/end`、界面上却没有任何错误提示。
|
|
22
33
|
*/
|
|
23
34
|
import type { Context, SessionId, SessionInput } from '../host/contracts.ts';
|
|
24
35
|
import type { AnnotationStore } from './model.ts';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-sidenote",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "DSH web plugin: Codex-style side chat (fork the current session into a persistent side panel) + selection annotations with numbered badges and a composer chip. A thin consumer of dsh-better-sidebar. / Codex 风格侧边聊天 + 划选注释的 DSH 插件。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,6 +19,17 @@
|
|
|
19
19
|
* plain 且草稿清空 = 发出(markSent 只翻当时拼进去的那批 id,C2 P1-3);
|
|
20
20
|
* 回到 plain 而草稿未清 = 发送失败(宿主 notice + 留稿)→ 仅在草稿仍以
|
|
21
21
|
* 我们拼的前缀开头时剥离回滚(C2 P2-3);相位未到终态前绝不回滚。
|
|
22
|
+
*
|
|
23
|
+
* 空草稿补位:宿主主按钮在 `draft.trim()==="" && attachments.length===0` 时
|
|
24
|
+
* 被置为原生 `disabled`(dsh-client-ui-conversation 的 `empty`),而注释/回流
|
|
25
|
+
* 按设计不进草稿 —— 于是此时**没有任何可达的发送手势**:浏览器不为 disabled
|
|
26
|
+
* 的按钮派发 `click`(`mousedown` 同样不发),只有 `pointerdown` 仍会到达它
|
|
27
|
+
* (Chromium / Firefox / WebKit 实测一致)。因此对「空草稿导致 disabled 的
|
|
28
|
+
* 主按钮」补一条 pointerdown 拦截;enabled 时一切照旧走 click,两条路径以
|
|
29
|
+
* `button.disabled` 互斥,不会双驱动。**该手势尾随的 click 必须一并吞掉**:
|
|
30
|
+
* 提交瞬间草稿变空,宿主主按钮随即按 `primaryStops = running && empty` 变身
|
|
31
|
+
* 「停止」,放行那一发 click 会立刻 `stop()` 掉刚发起的这一轮 —— 症状是
|
|
32
|
+
* `assistant/attempt` 空流、缺 `turn/end`、界面上却没有任何错误提示。
|
|
22
33
|
*/
|
|
23
34
|
import type { Context, ConversationService, SessionId, SessionInput } from '../host/contracts.ts'
|
|
24
35
|
import { buildProtocolBlock } from './format.ts'
|
|
@@ -56,6 +67,25 @@ export function installSendInterceptor(ctx: Context, store: AnnotationStore, ref
|
|
|
56
67
|
/** 重入/连按护栏:事务进行中吞掉命中识别面的 Enter/点击(不重复驱动)。 */
|
|
57
68
|
let committing = false
|
|
58
69
|
|
|
70
|
+
/**
|
|
71
|
+
* 手势尾随 click 的一次性吞并。pointerdown 提交后,同一次点击的 click 仍会
|
|
72
|
+
* 到达,而此时宿主主按钮已按 `primaryStops = running && empty` 变身「停止」
|
|
73
|
+
* ——放行它会立刻 `stop()` 掉刚发起的这一轮(表现:assistant/attempt 空流、
|
|
74
|
+
* 缺 turn/end、且没有任何错误提示)。只吞这一发,并带兜底超时。
|
|
75
|
+
*/
|
|
76
|
+
let swallowNextClick = false
|
|
77
|
+
let swallowTimer = 0
|
|
78
|
+
const clearSwallow = (): void => {
|
|
79
|
+
swallowNextClick = false
|
|
80
|
+
window.clearTimeout(swallowTimer)
|
|
81
|
+
}
|
|
82
|
+
const armSwallow = (): void => {
|
|
83
|
+
swallowNextClick = true
|
|
84
|
+
window.clearTimeout(swallowTimer)
|
|
85
|
+
// 兜底:手势没有产生 click(指针移出按钮等)时不至于吞掉后续无关点击。
|
|
86
|
+
swallowTimer = window.setTimeout(clearSwallow, 2_000)
|
|
87
|
+
}
|
|
88
|
+
|
|
59
89
|
const currentSessionId = (): string => {
|
|
60
90
|
try {
|
|
61
91
|
return ctx.sessions.list.getSnapshot().current ?? ''
|
|
@@ -159,6 +189,13 @@ export function installSendInterceptor(ctx: Context, store: AnnotationStore, ref
|
|
|
159
189
|
if (!(target instanceof Element)) return
|
|
160
190
|
const seat = target.closest('[data-composer-seat]')
|
|
161
191
|
if (seat === null) return
|
|
192
|
+
// 尾随 click:必须先于按钮识别吞掉(见 armSwallow),否则宿主按「停止」处理。
|
|
193
|
+
if (swallowNextClick) {
|
|
194
|
+
clearSwallow()
|
|
195
|
+
event.preventDefault()
|
|
196
|
+
event.stopImmediatePropagation()
|
|
197
|
+
return
|
|
198
|
+
}
|
|
162
199
|
const button = target.closest('button')
|
|
163
200
|
if (button === null) return
|
|
164
201
|
if (committing) {
|
|
@@ -175,10 +212,39 @@ export function installSendInterceptor(ctx: Context, store: AnnotationStore, ref
|
|
|
175
212
|
event.stopImmediatePropagation()
|
|
176
213
|
}
|
|
177
214
|
|
|
215
|
+
/**
|
|
216
|
+
* 空草稿补位:只接管「因空草稿而 disabled」的主按钮。enabled 时立即放行,
|
|
217
|
+
* 正常路径仍由上面的 click 拦截负责 —— 二者以 button.disabled 互斥。
|
|
218
|
+
*/
|
|
219
|
+
const onPointerDown = (event: PointerEvent): void => {
|
|
220
|
+
// 只认主键:右键/中键的 pointerdown 同样到达 disabled 按钮(已实证),
|
|
221
|
+
// 不检查会把「打开上下文菜单」误当发送手势。
|
|
222
|
+
if (event.button !== 0) return
|
|
223
|
+
const target = event.target
|
|
224
|
+
if (!(target instanceof Element)) return
|
|
225
|
+
const button = target.closest('button')
|
|
226
|
+
if (!(button instanceof HTMLButtonElement)) return
|
|
227
|
+
if (!button.disabled) return
|
|
228
|
+
if (button !== findSendButtonInCard()) return
|
|
229
|
+
if (committing) return
|
|
230
|
+
// disabled 的成因必须是空草稿:离线/无模型等其它成因不属本拦截器授权范围。
|
|
231
|
+
const sessionId = currentSessionId()
|
|
232
|
+
if (sessionId === '') return
|
|
233
|
+
const input = resolveInput(ctx, sessionId)
|
|
234
|
+
if (input === undefined || input.state.getSnapshot().draft.trim() !== '') return
|
|
235
|
+
if (!hijack()) return
|
|
236
|
+
// 同一次手势的 click 紧随其后,而主按钮此刻已变身「停止」——吞掉这一发。
|
|
237
|
+
armSwallow()
|
|
238
|
+
event.preventDefault()
|
|
239
|
+
event.stopImmediatePropagation()
|
|
240
|
+
}
|
|
241
|
+
|
|
178
242
|
document.addEventListener('keydown', onKeyDown, true)
|
|
179
243
|
document.addEventListener('click', onClick, true)
|
|
244
|
+
document.addEventListener('pointerdown', onPointerDown, true)
|
|
180
245
|
return () => {
|
|
181
246
|
document.removeEventListener('keydown', onKeyDown, true)
|
|
182
247
|
document.removeEventListener('click', onClick, true)
|
|
248
|
+
document.removeEventListener('pointerdown', onPointerDown, true)
|
|
183
249
|
}
|
|
184
250
|
}
|