dsh-sidenote 0.7.0 → 0.7.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.
Files changed (32) hide show
  1. package/README.md +3 -3
  2. package/lib/client.js +1131 -606
  3. package/lib/types/client/annotations/NativeComposer.d.ts +10 -0
  4. package/lib/types/client/annotations/NativeUserContext.d.ts +33 -2
  5. package/lib/types/client/annotations/chip.d.ts +2 -12
  6. package/lib/types/client/host/chat.d.ts +34 -0
  7. package/lib/types/client/host/contracts.d.ts +14 -1
  8. package/lib/types/client/host/dom.d.ts +5 -0
  9. package/lib/types/client/locales.d.ts +13 -0
  10. package/lib/types/client/sidechat/NativeConversation.d.ts +7 -0
  11. package/lib/types/client/sidechat/identity.d.ts +1 -1
  12. package/lib/types/client/sidechat/snapshot.d.ts +2 -0
  13. package/package.json +9 -4
  14. package/src/client/annotate/index.tsx +7 -16
  15. package/src/client/annotations/NativeComposer.tsx +110 -0
  16. package/src/client/annotations/NativeUserContext.tsx +168 -36
  17. package/src/client/annotations/annotations.module.css +60 -157
  18. package/src/client/annotations/chip.tsx +122 -118
  19. package/src/client/host/chat.ts +23 -19
  20. package/src/client/host/contracts.ts +5 -0
  21. package/src/client/host/dom.ts +13 -1
  22. package/src/client/locales.ts +30 -4
  23. package/src/client/reflow.ts +1 -1
  24. package/src/client/sidechat/NativeConversation.tsx +33 -16
  25. package/src/client/sidechat/SideChatPanel.tsx +8 -3
  26. package/src/client/sidechat/identity.ts +5 -1
  27. package/src/client/sidechat/sidechat.module.css +15 -1
  28. package/src/client/sidechat/snapshot.ts +7 -0
  29. package/lib/types/client/annotate/chip.d.ts +0 -10
  30. package/lib/types/client/annotate/send.d.ts +0 -8
  31. package/src/client/annotate/chip.tsx +0 -94
  32. package/src/client/annotate/send.ts +0 -155
package/lib/client.js CHANGED
@@ -31,6 +31,7 @@ window.__ModuleLoader__.load({
31
31
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
32
32
  _deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
33
33
  let react_dom_client = require("react-dom/client");
34
+ let react_dom = require("react-dom");
34
35
  //#region src/client/host/currentSession.ts
35
36
  /**
36
37
  * 当前(在屏)会话 id 的双版本探测:0.1.5 的 SessionListState 带 `current`
@@ -176,6 +177,20 @@ window.__ModuleLoader__.load({
176
177
  function sessionStatusSource(ctx) {
177
178
  return ctx.get("uiSession")?.sessionStatus;
178
179
  }
180
+ function completedReplySeqsOf(state) {
181
+ const result = [];
182
+ for (const turn of state.timeline.turnOrder) {
183
+ if (state.timeline.turns.get(turn)?.end?.data.reason.kind !== "completed") continue;
184
+ for (const key of [...state.locations.getTurn(turn)].reverse()) {
185
+ const node = state.nodes.get(key);
186
+ if (node?.kind !== "assistant-step") continue;
187
+ const data = node.data;
188
+ if (data.status === "settled" && data.finalNode && data.blocks?.some((block) => block.kind === "text" && block.text?.trim()) && !data.blocks.some((block) => block.kind === "tool-call")) result.push(data.finalNode.seq);
189
+ break;
190
+ }
191
+ }
192
+ return result;
193
+ }
179
194
  function chatSourceOf(ctx, binding) {
180
195
  const session = binding?.session;
181
196
  if (session === void 0) return void 0;
@@ -184,21 +199,7 @@ window.__ModuleLoader__.load({
184
199
  if (typeof target?.getSnapshot === "function" && typeof target.subscribe === "function") return {
185
200
  subscribe: (fn) => target.subscribe(fn),
186
201
  getLegacy: () => target.getSnapshot()?.legacy ?? null,
187
- getCompletedReplySeqs: () => {
188
- const state = target.getSnapshot();
189
- const result = [];
190
- for (const turn of state.timeline.turnOrder) {
191
- if (state.timeline.turns.get(turn)?.end?.data.reason.kind !== "completed") continue;
192
- for (const key of [...state.locations.getTurn(turn)].reverse()) {
193
- const node = state.nodes.get(key);
194
- if (node?.kind !== "assistant-step") continue;
195
- const data = node.data;
196
- if (data.status === "settled" && data.finalNode && data.blocks?.some((block) => block.kind === "text" && block.text?.trim()) && !data.blocks.some((block) => block.kind === "tool-call")) result.push(data.finalNode.seq);
197
- break;
198
- }
199
- }
200
- return result;
201
- },
202
+ getCompletedReplySeqs: () => completedReplySeqsOf(target.getSnapshot()),
202
203
  getLastTurnEnd: () => {
203
204
  const timeline = target.getSnapshot().timeline;
204
205
  if (!timeline) return void 0;
@@ -284,9 +285,6 @@ window.__ModuleLoader__.load({
284
285
  if (composer !== void 0 && composer.width > 0 && composer.left < clip.right && composer.right > clip.left) clip.bottom = Math.min(clip.bottom, composer.top);
285
286
  return clip;
286
287
  }
287
- function mainComposerOf(node) {
288
- return node.closest("[data-composer-seat]");
289
- }
290
288
  function focusMainComposer(view) {
291
289
  [...view.querySelectorAll("[data-composer-seat] [data-composer-input], [data-composer-seat] textarea")].find((input) => input.closest("[data-sidenote-chat-session]") === null)?.focus();
292
290
  }
@@ -302,22 +300,25 @@ window.__ModuleLoader__.load({
302
300
  function findSendButtonInCard(root = document) {
303
301
  const card = root.querySelector("[data-composer-card]");
304
302
  if (card === null) return null;
305
- const buttons = card.querySelectorAll("button");
303
+ const buttons = card.querySelectorAll("button:not([data-sidenote-submit])");
306
304
  const last = buttons[buttons.length - 1];
307
305
  return last instanceof HTMLButtonElement ? last : null;
308
306
  }
309
- function composerSessionId(seat, fallback) {
310
- return seat.closest("[data-sidenote-chat-session]")?.dataset.sidenoteChatSession ?? seat.closest("[data-conversation-session]")?.dataset.conversationSession ?? fallback;
311
- }
312
- function isMainComposerInput(target) {
313
- return target instanceof HTMLElement && (target.closest("[data-composer-input]") !== null || target instanceof HTMLTextAreaElement) && mainComposerOf(target) !== null;
314
- }
315
- function composerHasMenu(seat) {
316
- return seat.querySelector("[role=\"listbox\"], [role=\"menu\"], [aria-haspopup][aria-expanded=\"true\"]") !== null;
307
+ /** Bound message hover details to their native conversation pane. */
308
+ function conversationViewOf(node) {
309
+ return node.closest("[data-conversation-session], [data-sidenote-chat-session]") ?? document.body;
317
310
  }
318
311
  function hasConversationMessages() {
319
312
  return document.querySelector("[data-chat-flow-kind]") !== null;
320
313
  }
314
+ /** 0.2.0 has no UserText extension. Only add our own portal seat; never move
315
+ * or replace host-owned children. The CSS-module suffix is checked by E2E. */
316
+ function mountUserBubbleAddon(root, addon) {
317
+ root.querySelector("[class$=\"_bubble\"]")?.prepend(addon);
318
+ return () => {
319
+ addon.remove();
320
+ };
321
+ }
321
322
  //#endregion
322
323
  //#region src/client/host/sidebar.ts
323
324
  /** better-sidebar version that moved the right column to DSH's native sidebar. */
@@ -610,7 +611,7 @@ window.__ModuleLoader__.load({
610
611
  let dockRegistered = false;
611
612
  let report = {
612
613
  versions: {
613
- sidenote: "0.7.0",
614
+ sidenote: "0.7.2",
614
615
  dsh: "unknown",
615
616
  betterSidebar: null
616
617
  },
@@ -787,6 +788,14 @@ window.__ModuleLoader__.load({
787
788
  diagSideAcceptance: "Side message acceptance",
788
789
  inputUnavailable: "Cannot send right now. Body, attachments and quotes have been retained.",
789
790
  snapshotContext: "Main conversation progress at first send",
791
+ mainContext: "Main conversation context",
792
+ contextAtSend: "Updates on first send",
793
+ contextAsOf: "As of {time}",
794
+ contextInherited: "Inherited history",
795
+ contextrequest: "Request",
796
+ contextreply: "Reply",
797
+ contextthinking: "Reasoning",
798
+ contextstreaming: "In progress",
790
799
  inputWaiting: "The input is not ready. Body, attachments and quotes have been retained. Please try again shortly.",
791
800
  draftRecoveryConflict: "The host has another draft. Your local body is retained; review this session in the main view before retrying.",
792
801
  quoteWorkflowWaiting: "Annotations are not ready yet. Please try again shortly.",
@@ -877,6 +886,9 @@ window.__ModuleLoader__.load({
877
886
  notePlaceholder: "Add a note (optional)…",
878
887
  confirmTitle: "Confirm",
879
888
  saveNoteAria: "Save note",
889
+ editNoteAria: "Edit note",
890
+ selectedText: "Selected text",
891
+ userComment: "Your comment",
880
892
  sideNotePlaceholder: "Add a note for the side chat (optional)…",
881
893
  sideNoteAria: "Side chat note",
882
894
  confirmAskAria: "Confirm and ask",
@@ -897,15 +909,17 @@ window.__ModuleLoader__.load({
897
909
  sentBadgeTitle: "Sent annotation {n} (view only)",
898
910
  sentCardTitle: "Annotation {n} (sent)",
899
911
  clearAll: "Clear all",
912
+ clearAnnotations: "Clear annotations",
913
+ clearReflows: "Clear side-chat reflows",
900
914
  chipNote: " ({note})",
901
915
  headerSide: "Side",
902
916
  headerSideTitle: "Open a side chat (forked from the current session)",
903
917
  hintText: "Tip: select any text in a reply to annotate it or ask in a side chat",
904
918
  hintClose: "Got it",
905
- reflowToMain: "Send back to main session",
919
+ reflowToMain: "Bring back to main conversation",
906
920
  sendAnnotations: "Send annotations",
907
921
  reflowFrom: "From side chat ({title}):",
908
- reflowDone: "Added above the main composer",
922
+ reflowDone: "Added to main conversation, not sent yet",
909
923
  reflowFailed: "Couldn't reach the main session",
910
924
  reflowChipOne: "1 side-chat reflow",
911
925
  reflowChipMany: "{n} side-chat reflows",
@@ -988,6 +1002,14 @@ window.__ModuleLoader__.load({
988
1002
  diagSideAcceptance: "侧聊消息接受",
989
1003
  inputUnavailable: "暂时无法发送,正文、附件和注释已保留。",
990
1004
  snapshotContext: "首次发送时的主线进展",
1005
+ mainContext: "主线上下文",
1006
+ contextAtSend: "发送时更新",
1007
+ contextAsOf: "截至 {time}",
1008
+ contextInherited: "已继承主线历史",
1009
+ contextrequest: "请求",
1010
+ contextreply: "回答",
1011
+ contextthinking: "思考",
1012
+ contextstreaming: "正在输出",
991
1013
  inputWaiting: "输入尚未就绪,正文、附件和注释已保留,请稍后重试。",
992
1014
  draftRecoveryConflict: "宿主另有草稿,本地正文已保留;请在主视图核对该会话草稿后再试。",
993
1015
  quoteWorkflowWaiting: "注释功能暂未就绪,请稍后重试。",
@@ -1078,6 +1100,9 @@ window.__ModuleLoader__.load({
1078
1100
  notePlaceholder: "这里可以写自己的注解…",
1079
1101
  confirmTitle: "确认",
1080
1102
  saveNoteAria: "确认注解",
1103
+ editNoteAria: "编辑批注",
1104
+ selectedText: "所选文本",
1105
+ userComment: "用户评论",
1081
1106
  sideNotePlaceholder: "给侧边聊天写个注解(可空)…",
1082
1107
  sideNoteAria: "侧边聊天注解",
1083
1108
  confirmAskAria: "确认并提问",
@@ -1098,15 +1123,17 @@ window.__ModuleLoader__.load({
1098
1123
  sentBadgeTitle: "已发送的注释 {n}(只读回看)",
1099
1124
  sentCardTitle: "注释 {n}(已发送)",
1100
1125
  clearAll: "清空全部",
1126
+ clearAnnotations: "清空注释",
1127
+ clearReflows: "清空侧边回流",
1101
1128
  chipNote: "({note})",
1102
1129
  headerSide: "侧边",
1103
1130
  headerSideTitle: "打开侧边聊天(从当前会话 fork)",
1104
1131
  hintText: "小技巧:划选回复里的文字,可以加注解或在侧边提问",
1105
1132
  hintClose: "知道了",
1106
- reflowToMain: "回流到主会话",
1133
+ reflowToMain: "带回主对话",
1107
1134
  sendAnnotations: "发送注释",
1108
1135
  reflowFrom: "来自侧边聊天({title}):",
1109
- reflowDone: "已回流到主会话输入框上方",
1136
+ reflowDone: "已加入主对话,尚未发送",
1110
1137
  reflowFailed: "主会话不可达",
1111
1138
  reflowChipOne: "1 条侧边回流",
1112
1139
  reflowChipMany: "{n} 条侧边回流",
@@ -1235,15 +1262,15 @@ window.__ModuleLoader__.load({
1235
1262
  document.head.appendChild(tag);
1236
1263
  }
1237
1264
  var diagnostics_module_css_default = {
1265
+ "versions": "tGe0LW_versions",
1238
1266
  "trigger": "tGe0LW_trigger",
1239
- "setting": "tGe0LW_setting",
1267
+ "actions": "tGe0LW_actions",
1240
1268
  "panel": "tGe0LW_panel",
1241
- "list": "tGe0LW_list",
1242
- "header": "tGe0LW_header",
1243
1269
  "settingTitle": "tGe0LW_settingTitle",
1244
- "actions": "tGe0LW_actions",
1245
- "versions": "tGe0LW_versions",
1246
- "description": "tGe0LW_description"
1270
+ "setting": "tGe0LW_setting",
1271
+ "header": "tGe0LW_header",
1272
+ "description": "tGe0LW_description",
1273
+ "list": "tGe0LW_list"
1247
1274
  };
1248
1275
  //#endregion
1249
1276
  //#region src/client/diagnostics/index.tsx
@@ -1462,7 +1489,7 @@ window.__ModuleLoader__.load({
1462
1489
  /**
1463
1490
  * 回流通道的受控对象层(Delivery_02 Workitem_04 v2):侧边聊天的结论以
1464
1491
  * 「待回流对象」进入主会话——主 composer 上方的 chip(可预览/可移除),
1465
- * 发送瞬间由 annotate/send.ts 拦截器与注释协议块一起序列化进消息。
1492
+ * 发送瞬间由 annotations/NativeComposer.tsx 原生提交入口与注释协议块一起序列化进消息。
1466
1493
  * 不经草稿文本流(用户裁定:直接进草稿「很脏」)。
1467
1494
  *
1468
1495
  * 气泡留痕:序列化形态是 XML 协议块(见 buildReflowBlock),随注释协议块
@@ -2053,6 +2080,38 @@ window.__ModuleLoader__.load({
2053
2080
  };
2054
2081
  }
2055
2082
  //#endregion
2083
+ //#region src/client/host/icons.ts
2084
+ const EmptyIcon = () => null;
2085
+ const iconExports = _deepseek_ai_dsh_client_ui_primitives;
2086
+ /** Prefer the weight-named 0.1.7 export, then fall back to the legacy size-named export. */
2087
+ function resolveIcon(exports, modernName, legacyName) {
2088
+ return exports[modernName] ?? exports[legacyName] ?? EmptyIcon;
2089
+ }
2090
+ const icon = (modernName, legacyName) => resolveIcon(iconExports, modernName, legacyName);
2091
+ const IconNewChatOutline16 = icon("IconNewChatOutlineRegular", "IconNewChatOutline16");
2092
+ const IconCheckOutline16 = icon("IconCheckOutlineRegular", "IconCheckOutline16");
2093
+ const IconListPenOutline16 = icon("IconListPenOutlineRegular", "IconListPenOutline16");
2094
+ const IconEditOutline16 = icon("IconEditOutlineRegular", "IconEditOutline16");
2095
+ const IconTrashOutline16 = icon("IconTrashOutlineRegular", "IconTrashOutline16");
2096
+ icon("IconPlayOutlineRegular", "IconPlayOutline16");
2097
+ icon("IconCordisPluginOutlineRegular", "IconCordisPluginOutline14");
2098
+ icon("IconBranchOutlineRegular", "IconBranchOutline16");
2099
+ const IconShareOutline16 = icon("IconShareOutlineRegular", "IconShareOutline16");
2100
+ const IconChevronDownOutline14 = icon("IconChevronDownOutlineRegular", "IconChevronDownOutline14");
2101
+ const IconCloseOutline16 = icon("IconCloseOutlineRegular", "IconCloseOutline16");
2102
+ icon("IconRightUpOutlineRegular", "IconRightUpOutline16");
2103
+ icon("IconSearchOutlineRegular", "IconSearchOutline16");
2104
+ icon("IconGlobeOutlineRegular", "IconGlobeOutline14");
2105
+ icon("IconCodeOutlineRegular", "IconCodeOutline16");
2106
+ icon("IconChecklistOutlineRegular", "IconChecklistOutline14");
2107
+ icon("IconThinkOutlineRegular", "IconThinkOutline16");
2108
+ icon("IconInspectOutlineRegular", "IconInspectOutline12");
2109
+ icon("IconPaperclipOutlineRegular", "IconPaperclipOutline16");
2110
+ icon("IconStopFillRegular", "IconStopFill16");
2111
+ icon("IconSendOutlineRegular", "IconSendOutline16");
2112
+ icon("IconWarningOutlineRegular", "IconWarningOutline16");
2113
+ icon("IconQuestionOutlineRegular", "IconQuestionOutline14");
2114
+ //#endregion
2056
2115
  //#region src/client/annotations/format.ts
2057
2116
  /** 协议头(双语):zh「我批注了以下 2 处内容:」/ en 见 locales。 */
2058
2117
  function protocolHeader(n) {
@@ -2262,7 +2321,7 @@ window.__ModuleLoader__.load({
2262
2321
  }
2263
2322
  //#endregion
2264
2323
  //#region \0dsh-css:/home/runner/work/dsh-sidenote/dsh-sidenote/src/client/sidechat/sidechat.module.css.mjs
2265
- const css$2 = "._5KEEsG_nativeHost,._5KEEsG_root,._5KEEsG_nativeConversation{flex-direction:column;min-height:0;display:flex}._5KEEsG_nativeHost,._5KEEsG_root{height:100%}._5KEEsG_root{color:var(--dsw-alias-label-primary)}._5KEEsG_nativeConversation{flex:1}._5KEEsG_nativeToolbar{border-bottom:1px solid var(--dsw-alias-border-l1);justify-content:space-between;align-items:center;gap:8px;padding:8px 12px;display:flex}._5KEEsG_nativeToolbarActions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._5KEEsG_nativeAction,._5KEEsG_historyToggle{color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border:0;border-radius:6px;padding:4px 6px;font-size:13px}._5KEEsG_nativeAction:hover,._5KEEsG_nativeAction:focus-visible,._5KEEsG_historyToggle:hover,._5KEEsG_historyToggle:focus-visible{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._5KEEsG_nativeAction:disabled{opacity:.5;cursor:default}._5KEEsG_historyToggle{text-align:left;margin:8px 0;display:block}._5KEEsG_stateScreen{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:8px;padding:24px 16px;display:flex}._5KEEsG_emptyIcon{color:var(--dsw-alias-label-tertiary)}._5KEEsG_emptyTitle{font-size:14px;font-weight:600}._5KEEsG_emptyText{color:var(--dsw-alias-label-secondary);max-width:280px;font-size:13px;line-height:1.6}._5KEEsG_stateDetail,._5KEEsG_errorRow{color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;font-size:13px;line-height:1.6}._5KEEsG_errorRow{padding:8px 12px}._5KEEsG_headerSide{color:var(--dsw-alias-label-secondary);cursor:pointer;font:inherit;background:0 0;border:0;border-radius:6px;align-items:center;gap:4px;padding:4px 8px;display:inline-flex}._5KEEsG_headerSide:hover,._5KEEsG_headerSide:focus-visible{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._5KEEsG_headerSide:disabled{opacity:.4;cursor:default}._5KEEsG_visibilitySetting{justify-content:space-between;align-items:center;gap:16px;padding:8px 0;display:flex}._5KEEsG_visibilitySetting p{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:13px}";
2324
+ const css$2 = "._5KEEsG_nativeHost,._5KEEsG_root,._5KEEsG_nativeConversation{flex-direction:column;min-height:0;display:flex}._5KEEsG_nativeHost,._5KEEsG_root{height:100%}._5KEEsG_root{color:var(--dsw-alias-label-primary)}._5KEEsG_nativeConversation{flex:1}._5KEEsG_chatView{flex-direction:column;flex:1 0 auto;min-width:0;display:flex}._5KEEsG_mainContext{flex:none;padding:12px 24px 4px}._5KEEsG_historyToggle{align-items:center;gap:6px;display:flex}._5KEEsG_contextHint{color:var(--dsw-alias-label-tertiary);font-size:12px}._5KEEsG_contextChevron{transform:rotate(-90deg)}._5KEEsG_contextChevron,._5KEEsG_contextChevronOpen{flex:none}._5KEEsG_progressEntries{max-height:240px;margin:8px 6px;overflow:auto}._5KEEsG_progressEntry{white-space:pre-wrap;overflow-wrap:anywhere;margin:10px 0;font-size:13px;line-height:1.6}._5KEEsG_root [data-sidenote-progress-snapshot],._5KEEsG_root[data-sidenote-context-pending] [data-submission-echo]{display:none}._5KEEsG_reflowAction{width:32px;height:32px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:8px;flex:none;order:1;justify-content:center;align-items:center;margin-left:auto;padding:0;display:inline-flex}._5KEEsG_reflowAction:hover{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover)}._5KEEsG_reflowAction:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}._5KEEsG_reflowAction:disabled{cursor:default;color:var(--dsw-alias-state-business-primary);background:0 0}._5KEEsG_nativeToolbar{border-bottom:1px solid var(--dsw-alias-border-l1);justify-content:space-between;align-items:center;gap:8px;padding:8px 12px;display:flex}._5KEEsG_nativeToolbarActions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}._5KEEsG_nativeAction,._5KEEsG_historyToggle{color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;background:0 0;border:0;border-radius:6px;padding:4px 6px;font-size:13px}._5KEEsG_nativeAction:hover,._5KEEsG_nativeAction:focus-visible,._5KEEsG_historyToggle:hover,._5KEEsG_historyToggle:focus-visible{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._5KEEsG_nativeAction:disabled{opacity:.5;cursor:default}._5KEEsG_historyToggle{text-align:left;margin:0}._5KEEsG_stateScreen{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;gap:8px;padding:24px 16px;display:flex}._5KEEsG_emptyIcon{color:var(--dsw-alias-label-tertiary)}._5KEEsG_emptyTitle{font-size:14px;font-weight:600}._5KEEsG_emptyText{color:var(--dsw-alias-label-secondary);max-width:280px;font-size:13px;line-height:1.6}._5KEEsG_stateDetail,._5KEEsG_errorRow{color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;font-size:13px;line-height:1.6}._5KEEsG_errorRow{padding:8px 12px}._5KEEsG_headerSide{color:var(--dsw-alias-label-secondary);cursor:pointer;font:inherit;background:0 0;border:0;border-radius:6px;align-items:center;gap:4px;padding:4px 8px;display:inline-flex}._5KEEsG_headerSide:hover,._5KEEsG_headerSide:focus-visible{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}._5KEEsG_headerSide:disabled{opacity:.4;cursor:default}._5KEEsG_visibilitySetting{justify-content:space-between;align-items:center;gap:16px;padding:8px 0;display:flex}._5KEEsG_visibilitySetting p{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:13px}";
2266
2325
  const tagId$2 = "dsh-sidenote/sidechat.module.css";
2267
2326
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
2268
2327
  const tag = document.createElement("style");
@@ -2272,21 +2331,29 @@ window.__ModuleLoader__.load({
2272
2331
  document.head.appendChild(tag);
2273
2332
  }
2274
2333
  var sidechat_module_css_default = {
2275
- "stateScreen": "_5KEEsG_stateScreen",
2276
- "root": "_5KEEsG_root",
2277
2334
  "emptyText": "_5KEEsG_emptyText",
2278
- "stateDetail": "_5KEEsG_stateDetail",
2335
+ "headerSide": "_5KEEsG_headerSide",
2279
2336
  "nativeConversation": "_5KEEsG_nativeConversation",
2280
- "emptyIcon": "_5KEEsG_emptyIcon",
2281
- "visibilitySetting": "_5KEEsG_visibilitySetting",
2282
2337
  "errorRow": "_5KEEsG_errorRow",
2338
+ "nativeAction": "_5KEEsG_nativeAction",
2339
+ "stateDetail": "_5KEEsG_stateDetail",
2340
+ "progressEntries": "_5KEEsG_progressEntries",
2341
+ "contextChevron": "_5KEEsG_contextChevron",
2283
2342
  "nativeToolbar": "_5KEEsG_nativeToolbar",
2343
+ "mainContext": "_5KEEsG_mainContext",
2344
+ "root": "_5KEEsG_root",
2345
+ "nativeHost": "_5KEEsG_nativeHost",
2346
+ "contextHint": "_5KEEsG_contextHint",
2347
+ "visibilitySetting": "_5KEEsG_visibilitySetting",
2348
+ "progressEntry": "_5KEEsG_progressEntry",
2349
+ "reflowAction": "_5KEEsG_reflowAction",
2350
+ "emptyIcon": "_5KEEsG_emptyIcon",
2351
+ "nativeToolbarActions": "_5KEEsG_nativeToolbarActions",
2352
+ "contextChevronOpen": "_5KEEsG_contextChevronOpen",
2353
+ "stateScreen": "_5KEEsG_stateScreen",
2284
2354
  "emptyTitle": "_5KEEsG_emptyTitle",
2285
2355
  "historyToggle": "_5KEEsG_historyToggle",
2286
- "nativeToolbarActions": "_5KEEsG_nativeToolbarActions",
2287
- "nativeHost": "_5KEEsG_nativeHost",
2288
- "nativeAction": "_5KEEsG_nativeAction",
2289
- "headerSide": "_5KEEsG_headerSide"
2356
+ "chatView": "_5KEEsG_chatView"
2290
2357
  };
2291
2358
  //#endregion
2292
2359
  //#region src/client/sidechat/NativeConversation.tsx
@@ -2301,7 +2368,51 @@ window.__ModuleLoader__.load({
2301
2368
  });
2302
2369
  }
2303
2370
  function ChatView(props) {
2304
- return props.renderSlot("conversation.session", { view: "chat" });
2371
+ const side = (0, react.useContext)(SideContext);
2372
+ const fold = (0, react.useMemo)(() => createFoldStore(`dsh-sidenote:fold:v1:${side.sessionId}`), [side.sessionId]);
2373
+ (0, react.useSyncExternalStore)(fold.subscribe, fold.getSnapshot);
2374
+ const expanded = fold.isOpen("inherited");
2375
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2376
+ className: sidechat_module_css_default.chatView,
2377
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2378
+ className: sidechat_module_css_default.mainContext,
2379
+ "data-sidenote-main-context": true,
2380
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2381
+ type: "button",
2382
+ className: sidechat_module_css_default.historyToggle,
2383
+ "aria-expanded": expanded,
2384
+ onClick: () => {
2385
+ fold.toggle("inherited");
2386
+ },
2387
+ children: [
2388
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDownOutline14, {
2389
+ size: 12,
2390
+ className: expanded ? sidechat_module_css_default.contextChevronOpen : sidechat_module_css_default.contextChevron
2391
+ }),
2392
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("mainContext") }),
2393
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2394
+ className: sidechat_module_css_default.contextHint,
2395
+ children: side.contextPending ? t("contextAtSend") : side.progress?.takenAt ? t("contextAsOf", { time: side.progress.takenAt }) : t("contextInherited")
2396
+ })
2397
+ ]
2398
+ }), expanded && side.progress && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2399
+ className: sidechat_module_css_default.progressEntries,
2400
+ children: side.progress.entries.map((entry, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2401
+ className: sidechat_module_css_default.progressEntry,
2402
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2403
+ className: sidechat_module_css_default.contextHint,
2404
+ children: entry.kind === "tool" ? entry.toolName : t(`context${entry.kind}`)
2405
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: entry.text })]
2406
+ }, index))
2407
+ })]
2408
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SideContext.Provider, {
2409
+ value: {
2410
+ ...side,
2411
+ contextExpanded: expanded
2412
+ },
2413
+ children: props.renderSlot("conversation.session", { view: "chat" })
2414
+ })]
2415
+ });
2305
2416
  }
2306
2417
  function Content(props) {
2307
2418
  return props.renderFactorySlot("conversation.content", {
@@ -2337,12 +2448,13 @@ window.__ModuleLoader__.load({
2337
2448
  }
2338
2449
  function ReflowAction(props) {
2339
2450
  const side = (0, react.useContext)(SideContext);
2340
- const legacy = props.useChat((state) => state.legacy);
2341
- if (side === null || props.turn.end?.data.reason.kind !== "completed") return null;
2451
+ const state = props.useChat((state) => state);
2452
+ const node = state.legacy?.nodes.find((node) => node.messageId === props.messageId);
2453
+ if (side === null || !node || !completedReplySeqsOf(state).includes(node.seq)) return null;
2342
2454
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReplyAction, {
2343
2455
  side,
2344
- messages: transcriptOf(legacy ?? null),
2345
- seq: props.seq
2456
+ messages: transcriptOf(state.legacy ?? null),
2457
+ seq: node.seq
2346
2458
  });
2347
2459
  }
2348
2460
  function ReplyAction({ side, messages, seq }) {
@@ -2351,20 +2463,35 @@ window.__ModuleLoader__.load({
2351
2463
  if (!reply) return null;
2352
2464
  const question = pairQuestions(messages).get(reply.key);
2353
2465
  const added = isReplyAdded(side, reply, question);
2354
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2355
- type: "button",
2356
- className: sidechat_module_css_default.nativeAction,
2357
- "aria-pressed": added,
2358
- disabled: added,
2359
- onClick: () => {
2360
- addReply(side, reply, question);
2361
- },
2362
- children: t(added ? "reflowDone" : "reflowToMain")
2466
+ const label = t(added ? "reflowDone" : "reflowToMain");
2467
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
2468
+ label,
2469
+ side: "top",
2470
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2471
+ type: "button",
2472
+ className: sidechat_module_css_default.reflowAction,
2473
+ "aria-label": label,
2474
+ "aria-pressed": added,
2475
+ disabled: added,
2476
+ onClick: () => {
2477
+ addReply(side, reply, question);
2478
+ },
2479
+ children: added ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCheckOutline16, { size: 18 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2480
+ width: "18",
2481
+ height: "18",
2482
+ viewBox: "0 0 24 24",
2483
+ fill: "none",
2484
+ stroke: "currentColor",
2485
+ strokeWidth: "1.6",
2486
+ strokeLinecap: "round",
2487
+ strokeLinejoin: "round",
2488
+ "aria-hidden": "true",
2489
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4 4v16M20 12H8m5-5-5 5 5 5" })
2490
+ })
2491
+ })
2363
2492
  });
2364
2493
  }
2365
2494
  function HistoryView({ Original, props, side }) {
2366
- const fold = (0, react.useMemo)(() => createFoldStore(`dsh-sidenote:fold:v1:${side.sessionId}`), [side.sessionId]);
2367
- (0, react.useSyncExternalStore)(fold.subscribe, fold.getSnapshot);
2368
2495
  const nodes = props.useChat((state) => state.nodes);
2369
2496
  const snapshot = props.useConversation((state) => state);
2370
2497
  const groups = snapshot.views.grouped("chat");
@@ -2378,9 +2505,7 @@ window.__ModuleLoader__.load({
2378
2505
  const members = groups?.groupSource(entry.key).getSnapshot()?.members ?? [];
2379
2506
  return members.length > 0 && members.every(isInherited);
2380
2507
  };
2381
- const inherited = entries.filter(isInherited);
2382
- const expanded = fold.isOpen("inherited");
2383
- const visible = expanded ? entries : entries.filter((entry) => !isInherited(entry));
2508
+ const visible = side.contextExpanded ? entries : entries.filter((entry) => !isInherited(entry));
2384
2509
  const projected = {
2385
2510
  ...snapshot,
2386
2511
  views: {
@@ -2392,18 +2517,10 @@ window.__ModuleLoader__.load({
2392
2517
  }
2393
2518
  };
2394
2519
  const useProjected = (0, react.useCallback)((selector) => selector(projected), [projected]);
2395
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [inherited.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2396
- type: "button",
2397
- className: sidechat_module_css_default.historyToggle,
2398
- "aria-expanded": expanded,
2399
- onClick: () => {
2400
- fold.toggle("inherited");
2401
- },
2402
- children: t("inheritedLabel", { n: inherited.length })
2403
- }), (0, react.createElement)(Original, {
2520
+ return (0, react.createElement)(Original, {
2404
2521
  ...props,
2405
2522
  useConversation: useProjected
2406
- })] });
2523
+ });
2407
2524
  }
2408
2525
  /** Both admitted hosts publish the native chat View; only 0.2.1 adds a Flow slot. */
2409
2526
  function registerInheritedHistory(ctx) {
@@ -2484,47 +2601,15 @@ window.__ModuleLoader__.load({
2484
2601
  }
2485
2602
  function registerNativeConversation(ctx) {
2486
2603
  ctx.slots.inject("sidenote.conversation", () => ctx.slots.register({ name: "sidenote.conversation" }, Content));
2487
- ctx.slots.inject("conversation.chat.turnTail", () => ctx.slots.register({
2488
- name: "conversation.chat.turnTail",
2604
+ ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register({
2605
+ name: "conversation.chat.assistant-actions",
2489
2606
  id: "dsh-sidenote-reflow",
2490
- order: 20
2607
+ order: 100
2491
2608
  }, ReflowAction));
2492
2609
  registerInheritedHistory(ctx);
2493
2610
  registerAssistantAnchors(ctx);
2494
2611
  }
2495
2612
  //#endregion
2496
- //#region src/client/host/icons.ts
2497
- const EmptyIcon = () => null;
2498
- const iconExports = _deepseek_ai_dsh_client_ui_primitives;
2499
- /** Prefer the weight-named 0.1.7 export, then fall back to the legacy size-named export. */
2500
- function resolveIcon(exports, modernName, legacyName) {
2501
- return exports[modernName] ?? exports[legacyName] ?? EmptyIcon;
2502
- }
2503
- const icon = (modernName, legacyName) => resolveIcon(iconExports, modernName, legacyName);
2504
- const IconNewChatOutline16 = icon("IconNewChatOutlineRegular", "IconNewChatOutline16");
2505
- const IconCheckOutline16 = icon("IconCheckOutlineRegular", "IconCheckOutline16");
2506
- const IconListPenOutline16 = icon("IconListPenOutlineRegular", "IconListPenOutline16");
2507
- icon("IconEditOutlineRegular", "IconEditOutline16");
2508
- const IconTrashOutline16 = icon("IconTrashOutlineRegular", "IconTrashOutline16");
2509
- icon("IconPlayOutlineRegular", "IconPlayOutline16");
2510
- icon("IconCordisPluginOutlineRegular", "IconCordisPluginOutline14");
2511
- icon("IconBranchOutlineRegular", "IconBranchOutline16");
2512
- const IconShareOutline16 = icon("IconShareOutlineRegular", "IconShareOutline16");
2513
- icon("IconChevronDownOutlineRegular", "IconChevronDownOutline14");
2514
- const IconCloseOutline16 = icon("IconCloseOutlineRegular", "IconCloseOutline16");
2515
- icon("IconRightUpOutlineRegular", "IconRightUpOutline16");
2516
- icon("IconSearchOutlineRegular", "IconSearchOutline16");
2517
- icon("IconGlobeOutlineRegular", "IconGlobeOutline14");
2518
- icon("IconCodeOutlineRegular", "IconCodeOutline16");
2519
- icon("IconChecklistOutlineRegular", "IconChecklistOutline14");
2520
- icon("IconThinkOutlineRegular", "IconThinkOutline16");
2521
- icon("IconInspectOutlineRegular", "IconInspectOutline12");
2522
- icon("IconPaperclipOutlineRegular", "IconPaperclipOutline16");
2523
- icon("IconStopFillRegular", "IconStopFill16");
2524
- icon("IconSendOutlineRegular", "IconSendOutline16");
2525
- icon("IconWarningOutlineRegular", "IconWarningOutline16");
2526
- icon("IconQuestionOutlineRegular", "IconQuestionOutline14");
2527
- //#endregion
2528
2613
  //#region src/client/sidechat/composer.ts
2529
2614
  /** Programmatic draft handoff only. The embedded native composer owns editing and submission. */
2530
2615
  function resolveSessionInput(ctx, sessionId) {
@@ -2864,12 +2949,15 @@ window.__ModuleLoader__.load({
2864
2949
  /**
2865
2950
  * 首条用户消息 → topic。折叠全部空白(含换行)为单空格,截 20 码点,
2866
2951
  * 超长加省略号;清洗后为空返回 undefined(调用方回退编号标题)。
2867
- * 快照前缀(Delivery_04)在 composer 层就不进 body,这里无需剥离。
2952
+ * 已落盘消息也会用于恢复主题;先剥离快照及批注/回流协议。
2868
2953
  * 划选提问的种子草稿是「> 引用行 + note 行」(buildSideChatQuote):
2869
2954
  * 引用行剥除(用户自己的话才是主题),机械占位行(noNote)丢弃;
2870
2955
  * 剥完为空(未补充直接发送)时退回引用内容——引用即主题。
2871
2956
  */
2872
2957
  function topicOf(text) {
2958
+ text = splitSnapshotPrefix(text)?.rest ?? text;
2959
+ const protocol = splitProtocolPrefix(text);
2960
+ if (protocol) text = text.slice(protocol.length).trim() || protocol.annotations.map((item) => item.note || item.quote).join(" ") || protocol.reflows.map((item) => item.source).join(" ");
2873
2961
  const lines = text.split("\n");
2874
2962
  const clean = lines.filter((l) => !l.startsWith(">")).filter((l) => l.trim() !== t("noNote")).join(" ").replaceAll(/\s+/gu, " ").trim();
2875
2963
  if (clean !== "") return truncate(clean);
@@ -3153,6 +3241,21 @@ window.__ModuleLoader__.load({
3153
3241
  }
3154
3242
  //#endregion
3155
3243
  //#region src/client/sidechat/snapshot.ts
3244
+ /** Read the plugin's persisted progress block as plain, expandable content. */
3245
+ function snapshotEntriesFromText(text) {
3246
+ const kinds = {
3247
+ "user-request": "request",
3248
+ reply: "reply",
3249
+ thinking: "thinking",
3250
+ "tool-call": "tool",
3251
+ streaming: "streaming"
3252
+ };
3253
+ return [...text.matchAll(/<(user-request|reply|thinking|tool-call|streaming)(?: name="([^"]*)")?>([\s\S]*?)<\/\1>/g)].map((match) => ({
3254
+ kind: kinds[match[1]],
3255
+ text: match[3].trim(),
3256
+ ...match[2] ? { toolName: match[2] } : {}
3257
+ }));
3258
+ }
3156
3259
  function blocksText(blocks, kinds) {
3157
3260
  if (!Array.isArray(blocks)) return "";
3158
3261
  const parts = [];
@@ -4262,6 +4365,22 @@ ${entries.map((entry) => {
4262
4365
  }, [visible]);
4263
4366
  const parent = meta.parentSessionId ? ctx.sessions.binding(meta.parentSessionId)?.session : void 0;
4264
4367
  const parentSnapshot = (0, react.useSyncExternalStore)((0, react.useCallback)((notify) => parent?.subscribe(notify) ?? NOOP_UNSUBSCRIBE, [parent]), () => parent?.getSnapshot() ?? null);
4368
+ const parentChat = (0, react.useMemo)(() => chatSourceOf(ctx, ctx.sessions.binding(meta.parentSessionId ?? scope.sessionId)), [
4369
+ ctx,
4370
+ parent,
4371
+ meta.parentSessionId,
4372
+ scope.sessionId
4373
+ ]);
4374
+ const parentLegacy = (0, react.useSyncExternalStore)((0, react.useCallback)((notify) => parentChat?.subscribe(notify) ?? NOOP_UNSUBSCRIBE, [parentChat]), () => parentChat?.getLegacy() ?? null);
4375
+ const savedProgress = (0, react.useMemo)(() => messages.filter((message) => message.role === "user" && (message.seq ?? -1) > (meta.boundarySeq ?? -1)).map((message) => splitSnapshotPrefix(message.text)).find(Boolean), [messages, meta.boundarySeq]);
4376
+ const progress = (0, react.useMemo)(() => savedProgress ? {
4377
+ entries: snapshotEntriesFromText(savedProgress.snapshot),
4378
+ takenAt: savedProgress.takenAt
4379
+ } : meta.forkedMidTurn ? { entries: snapshotEntriesOf(parentLegacy?.nodes ?? [], parentLegacy?.partial) } : void 0, [
4380
+ savedProgress,
4381
+ meta.forkedMidTurn,
4382
+ parentLegacy
4383
+ ]);
4265
4384
  const status = sessionStatusSource(ctx);
4266
4385
  const parentPending = (0, react.useSyncExternalStore)((0, react.useCallback)((notify) => status?.subscribe(notify) ?? NOOP_UNSUBSCRIBE, [status]), () => status?.getSnapshot().get(meta.parentSessionId ?? scope.sessionId)?.pendingInteraction != null);
4267
4386
  const [saving, setSaving] = (0, react.useState)(false);
@@ -4297,7 +4416,9 @@ ${entries.map((entry) => {
4297
4416
  parentSessionId: meta.parentSessionId ?? scope.sessionId,
4298
4417
  title: tab.title,
4299
4418
  boundarySeq: meta.boundarySeq,
4300
- reflow: props.reflow
4419
+ reflow: props.reflow,
4420
+ progress,
4421
+ contextPending: meta.forkedMidTurn && !savedProgress
4301
4422
  };
4302
4423
  const replies = finalReplies(messages, meta.boundarySeq, new Set(chat?.getCompletedReplySeqs?.() ?? []));
4303
4424
  const questions = pairQuestions(messages);
@@ -4309,6 +4430,7 @@ ${entries.map((entry) => {
4309
4430
  className: sidechat_module_css_default.root,
4310
4431
  "data-sidenote-chat-session": childId,
4311
4432
  "data-sidenote-view-title": tab.title,
4433
+ "data-sidenote-context-pending": side.contextPending || void 0,
4312
4434
  children: [
4313
4435
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4314
4436
  className: sidechat_module_css_default.nativeToolbar,
@@ -5431,34 +5553,34 @@ ${entries.map((entry) => {
5431
5553
  document.head.appendChild(tag);
5432
5554
  }
5433
5555
  var annotate_module_css_default = {
5434
- "editorTextarea": "_SVViG_editorTextarea",
5435
- "editorNew": "_SVViG_editorNew",
5436
- "sentCardTitle": "_SVViG_sentCardTitle",
5556
+ "dshSidenoteHintIn": "_SVViG_dshSidenoteHintIn",
5557
+ "deleteButton": "_SVViG_deleteButton",
5558
+ "editorEdit": "_SVViG_editorEdit",
5437
5559
  "hintClose": "_SVViG_hintClose",
5438
- "sentChipRow": "_SVViG_sentChipRow",
5439
5560
  "editorTarget": "_SVViG_editorTarget",
5440
- "editorEdit": "_SVViG_editorEdit",
5561
+ "sentChipRow": "_SVViG_sentChipRow",
5562
+ "editorInput": "_SVViG_editorInput",
5563
+ "toolbarButton": "_SVViG_toolbarButton",
5441
5564
  "highlight": "_SVViG_highlight",
5442
- "dshSidenoteHintIn": "_SVViG_dshSidenoteHintIn",
5443
- "sentCardQuote": "_SVViG_sentCardQuote",
5444
- "sentCardNote": "_SVViG_sentCardNote",
5445
- "saveButton": "_SVViG_saveButton",
5565
+ "sentChip": "_SVViG_sentChip",
5566
+ "editorTextarea": "_SVViG_editorTextarea",
5567
+ "editorNew": "_SVViG_editorNew",
5446
5568
  "toolbarBelow": "_SVViG_toolbarBelow",
5447
- "editorAskRow": "_SVViG_editorAskRow",
5448
- "toolbarButton": "_SVViG_toolbarButton",
5569
+ "badgeSent": "_SVViG_badgeSent",
5449
5570
  "toolbar": "_SVViG_toolbar",
5571
+ "editorFooter": "_SVViG_editorFooter",
5450
5572
  "editorAsk": "_SVViG_editorAsk",
5451
5573
  "confirmButton": "_SVViG_confirmButton",
5452
- "editorFooter": "_SVViG_editorFooter",
5453
- "editorSpacer": "_SVViG_editorSpacer",
5454
- "deleteButton": "_SVViG_deleteButton",
5574
+ "hint": "_SVViG_hint",
5455
5575
  "badge": "_SVViG_badge",
5456
- "editorError": "_SVViG_editorError",
5457
- "badgeSent": "_SVViG_badgeSent",
5458
- "editorInput": "_SVViG_editorInput",
5576
+ "saveButton": "_SVViG_saveButton",
5577
+ "sentCardNote": "_SVViG_sentCardNote",
5459
5578
  "cancelButton": "_SVViG_cancelButton",
5460
- "sentChip": "_SVViG_sentChip",
5461
- "hint": "_SVViG_hint"
5579
+ "editorAskRow": "_SVViG_editorAskRow",
5580
+ "editorSpacer": "_SVViG_editorSpacer",
5581
+ "editorError": "_SVViG_editorError",
5582
+ "sentCardTitle": "_SVViG_sentCardTitle",
5583
+ "sentCardQuote": "_SVViG_sentCardQuote"
5462
5584
  };
5463
5585
  //#endregion
5464
5586
  //#region src/client/annotate/hint.tsx
@@ -6260,527 +6382,624 @@ ${entries.map((entry) => {
6260
6382
  });
6261
6383
  }
6262
6384
  //#endregion
6263
- //#region src/client/annotations/send.ts
6264
- /** Main and side composers use this same native transaction and acceptance boundary. */
6265
- async function submitAnnotations(options) {
6266
- const { ctx, store, sessionId, input, annotations } = options;
6267
- cancelInputRollback(input);
6268
- const parts = [...options.leadingBlocks ?? []];
6269
- if (annotations.length > 0) parts.push(buildProtocolBlock(annotations));
6270
- const block = parts.join("\n\n");
6271
- if (block === "") return false;
6272
- const draftBeforeWait = input.state.getSnapshot();
6273
- let prefix = "";
6274
- let receipt;
6275
- try {
6276
- receipt = await prepareSubmission(ctx, sessionId, input, block);
6277
- const draft = input.state.getSnapshot();
6278
- if (draft.draft !== draftBeforeWait.draft || draft.draftRev !== draftBeforeWait.draftRev) throw new Error("等待会话就绪期间草稿已改变;正文和注释已保留,请重新发送");
6279
- prefix = block + (draft.draft === "" ? "" : "\n\n");
6280
- if (!prependInputText(input, prefix)) throw new Error("宿主输入尚未就绪,无法安全加入引用;内容已保留");
6281
- receipt.submit(options.mode);
6282
- await receipt.outcome;
6283
- store.markSent(annotations.filter((annotation) => store.get(annotation.id) === annotation).map((annotation) => annotation.id));
6284
- return true;
6285
- } catch (error) {
6286
- rollbackInputPrefix(input, prefix);
6287
- const text = error instanceof Error ? error.message : String(error);
6288
- store.setSubmission(sessionId, {
6289
- pending: false,
6290
- error: text
6291
- });
6292
- try {
6293
- if (typeof input.notify === "function") input.notify("error", text);
6294
- } catch {}
6295
- return false;
6296
- } finally {
6297
- receipt?.dispose();
6298
- }
6299
- }
6300
- /** Shared by native input gestures and the explicit annotation/reflow actions. */
6301
- async function submitControlledInput(ctx, store, reflow, sessionId, mode) {
6302
- if (store.getSubmission(sessionId).pending) return false;
6303
- const input = resolveInput(ctx, sessionId);
6304
- if (!input) {
6305
- store.setSubmission(sessionId, {
6306
- pending: false,
6307
- error: t("inputUnavailable")
6308
- });
6309
- return false;
6310
- }
6311
- const draft = input.state.getSnapshot();
6312
- if (draft.phase !== "plain" || draft.draft.trimStart().startsWith("/")) return false;
6313
- const active = store.listActive(sessionId);
6314
- const reflows = reflow.list(sessionId);
6315
- if (draft.draft.trim() === "" && inputAttachmentIds(input).length === 0 && active.length === 0 && reflows.length === 0) return false;
6316
- const extras = submissionExtrasOf(sessionId);
6317
- const prefix = extras?.firstSendPrefix();
6318
- store.setSubmission(sessionId, { pending: true });
6319
- try {
6320
- const { injectedIds } = await tryInjectReflows(sessionId, reflows);
6321
- for (const id of injectedIds) reflow.remove(id);
6322
- const hitchhikers = reflows.filter((item) => !injectedIds.has(item.id));
6323
- if (active.length === 0 && hitchhikers.length === 0 && !prefix) {
6324
- if (input.state.getSnapshot().draft.trim() !== "" || inputAttachmentIds(input).length > 0) input.submit(mode);
6325
- return true;
6326
- }
6327
- const accepted = await submitAnnotations({
6328
- ctx,
6329
- store,
6330
- sessionId,
6331
- input,
6332
- annotations: active,
6333
- leadingBlocks: [...prefix ? [prefix] : [], ...hitchhikers.map(buildReflowBlock)],
6334
- mode
6335
- });
6336
- if (accepted) {
6337
- for (const item of hitchhikers) reflow.remove(item.id);
6338
- extras?.onSubmitText(draft.draft);
6339
- extras?.onAccepted();
6340
- }
6341
- return accepted;
6342
- } catch (error) {
6343
- store.setSubmission(sessionId, {
6344
- pending: false,
6345
- error: error instanceof Error ? error.message : String(error)
6346
- });
6347
- return false;
6348
- } finally {
6349
- store.setSubmission(sessionId, {
6350
- ...store.getSubmission(sessionId),
6351
- pending: false
6352
- });
6353
- }
6354
- }
6355
- //#endregion
6356
- //#region \0dsh-css:/home/runner/work/dsh-sidenote/dsh-sidenote/src/client/annotations/annotations.module.css.mjs
6357
- const css = ".SVnebq_chipWrap{box-sizing:border-box;width:calc(100% - var(--dsh-composer-side-clearance,16px) - var(--dsh-composer-side-clearance,16px));max-width:var(--dsh-composer-card-max-width,780px);flex-direction:column;align-items:flex-start;gap:4px;margin:0 auto;display:flex;position:relative}.SVnebq_chip{border:1px solid var(--dsw-alias-border-l1,#0000001a);background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#1f2329);cursor:pointer;user-select:none;border-radius:999px;align-items:center;gap:5px;padding:2px 10px;font-size:12px;line-height:18px;display:inline-flex}.SVnebq_chip:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000d)}.SVnebq_chipPanel{z-index:50;border:1px solid var(--dsw-alias-border-l1,#00000014);background:var(--dsw-alias-bg-layer-1,#fff);border-radius:12px;min-width:220px;max-width:420px;margin:0;padding:4px;list-style:none;position:absolute;bottom:calc(100% + 4px);left:0;box-shadow:0 6px 20px #0000001a}.SVnebq_chipRow{border-radius:8px;align-items:center;gap:8px;padding:4px 6px;display:flex}.SVnebq_chipRow:hover{background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.SVnebq_chipNumber{color:#fff;text-align:center;background:#2f88ff;border-radius:999px;flex:none;width:16px;height:16px;font-size:10px;font-weight:600;line-height:16px}.SVnebq_chipText{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary,#1f2329);flex:auto;font-size:12px;overflow:hidden}.SVnebq_chipNote{color:var(--dsw-alias-label-tertiary,#8f959e)}.SVnebq_chipRemove{width:20px;height:20px;color:var(--dsw-alias-label-tertiary,#8f959e);cursor:pointer;background:0 0;border:none;border-radius:999px;flex:none;place-items:center;padding:0;display:grid}.SVnebq_chipRemove:hover{background:var(--dsw-alias-interactive-bg-hover,#00000014);color:var(--dsw-alias-state-error-primary,#d53f3f)}.SVnebq_chipClearAll{color:var(--dsw-alias-label-tertiary,#8f959e);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:2px 6px;font-size:12px;line-height:18px}.SVnebq_chipClearAll:hover{color:var(--dsw-alias-state-error-primary,#d53f3f);background:var(--dsw-alias-interactive-bg-hover,#0000000a)}.SVnebq_chipWrap{background:var(--dsw-alias-bg-base,#fff);padding:2px 0}.SVnebq_chipPanel:after{content:\"\";background:var(--dsw-alias-bg-layer-1,#fff);border-right:1px solid var(--dsw-alias-border-l1,#00000014);border-bottom:1px solid var(--dsw-alias-border-l1,#00000014);width:10px;height:10px;position:absolute;bottom:-5px;left:18px;transform:rotate(45deg)}.SVnebq_side{width:100%;max-width:none;margin:0}.SVnebq_storageNotice{color:var(--dsw-alias-label-secondary,#646a73);font-size:12px}.SVnebq_sentContext{color:var(--dsw-alias-label-secondary);margin:8px 0;font-size:13px}.SVnebq_sentContext summary{cursor:pointer}.SVnebq_sentContext pre,.SVnebq_sentContext blockquote{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit}";
6358
- const tagId = "dsh-sidenote/annotations.module.css";
6359
- if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
6360
- const tag = document.createElement("style");
6361
- tag.dataset.plugin = "dsh-sidenote";
6362
- tag.dataset.pluginCss = tagId;
6363
- tag.textContent = css;
6364
- document.head.appendChild(tag);
6385
+ //#region \0dsh-css:/home/runner/work/dsh-sidenote/dsh-sidenote/src/client/annotations/annotations.module.css.mjs
6386
+ const css = ".SVnebq_chipWrap{box-sizing:border-box;width:calc(100% - 2 * var(--dsh-composer-side-clearance,16px));max-width:var(--dsh-composer-card-max-width,780px);background:var(--dsw-alias-bg-base);flex-wrap:wrap;align-items:center;gap:6px;margin:0 auto;padding:2px 0;display:flex}.SVnebq_chip{--chip-surface:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);background:var(--chip-surface);color:var(--dsw-alias-label-secondary);border-radius:10px;align-items:center;transition:background .12s,color .12s;display:inline-flex;position:relative}.SVnebq_chip:hover,.SVnebq_chip:focus-within,.SVnebq_chip[data-open]{--chip-surface:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-label-primary)}.SVnebq_chipMain{border-radius:inherit;min-height:28px;color:inherit;font:inherit;cursor:pointer;background:0 0;border:0;align-items:center;gap:5px;padding:3px 9px;font-size:12px;line-height:18px;display:inline-flex}.SVnebq_chipDismissShade{border-radius:inherit;background:linear-gradient(to right, transparent, var(--chip-surface) 45%);opacity:0;pointer-events:none;justify-content:flex-end;align-items:center;width:38px;transition:opacity .16s ease-out;display:flex;position:absolute;inset:0 3px 0 auto}.SVnebq_chipDismiss{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);width:24px;height:24px;color:var(--dsw-alias-label-secondary);cursor:pointer;border-radius:50%;flex:none;justify-content:center;align-items:center;padding:0;transition:transform .16s ease-out,background .12s,color .12s;display:inline-flex;transform:translate(2px)scale(.94)}.SVnebq_chip:hover .SVnebq_chipDismissShade,.SVnebq_chip:focus-within .SVnebq_chipDismissShade{opacity:1}.SVnebq_chip:hover .SVnebq_chipDismiss,.SVnebq_chip:focus-within .SVnebq_chipDismiss{pointer-events:auto;transform:none}.SVnebq_chipDismiss:hover{background:var(--dsw-alias-interactive-bg-hover-solid);color:var(--dsw-alias-label-primary)}.SVnebq_chipDismiss:disabled{opacity:.45;cursor:default}.SVnebq_contextPanel{box-sizing:border-box;z-index:1000;overscroll-behavior:contain;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:14px;padding:4px;animation:.12s ease-out SVnebq_contextAppear;position:fixed;overflow:auto;box-shadow:0 8px 28px #0000001f}.SVnebq_contextList{margin:0;padding:0;list-style:none}.SVnebq_contextItem{border-radius:10px;align-items:flex-start;gap:10px;margin:0;padding:8px 10px;transition:background .12s;display:flex}.SVnebq_contextItem+.SVnebq_contextItem{border-top:1px solid var(--dsw-alias-border-l1)}.SVnebq_contextItem:hover,.SVnebq_contextItem:focus-within{background:var(--dsw-alias-interactive-bg-hover)}.SVnebq_contextNumber{min-width:16px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;flex:none;font-size:12px;line-height:21px}.SVnebq_contextContent{flex:1;min-width:0}.SVnebq_contextLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.SVnebq_contextLabel:not(:first-child){margin-top:6px}.SVnebq_contextSource{color:var(--dsw-alias-label-secondary);margin-bottom:8px;font-size:12px}.SVnebq_contextText{white-space:pre-wrap;overflow-wrap:anywhere;user-select:text;font-size:13px;line-height:1.6}.SVnebq_contextActions{flex:none;gap:2px;display:flex}.SVnebq_contextAction{width:26px;height:26px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:8px;justify-content:center;align-items:center;padding:0;transition:color .12s,background .12s;display:inline-flex}.SVnebq_contextAction:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.SVnebq_contextAction:disabled,.SVnebq_chipClearAll:disabled{opacity:.45;cursor:default}.SVnebq_contextEditor{box-sizing:border-box;resize:vertical;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-base);width:100%;min-height:64px;color:inherit;font:inherit;border-radius:6px;margin-top:4px;padding:6px 8px;font-size:13px;line-height:1.6;display:block}.SVnebq_contextFooter{padding:2px 10px 4px}.SVnebq_chipClearAll{color:var(--dsw-alias-label-tertiary);cursor:pointer;font:inherit;background:0 0;border:0;border-radius:6px;padding:4px 6px;font-size:12px}.SVnebq_chipClearAll:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-state-error-primary)}.SVnebq_chipMain:focus-visible,.SVnebq_chipDismiss:focus-visible,.SVnebq_contextAction:focus-visible,.SVnebq_chipClearAll:focus-visible,.SVnebq_contextEditor:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.SVnebq_storageNotice{color:var(--dsw-alias-label-secondary);flex-basis:100%;font-size:12px}@keyframes SVnebq_contextAppear{0%{opacity:0;transform:translateY(3px)}to{opacity:1;transform:translateY(0)}}@media (hover:none){.SVnebq_chipMain{padding-right:32px}.SVnebq_chipDismissShade{opacity:1}.SVnebq_chipDismiss{pointer-events:auto;transform:none}}@media (prefers-reduced-motion:reduce){.SVnebq_contextPanel{animation:none}.SVnebq_chip,.SVnebq_chipDismissShade,.SVnebq_chipDismiss,.SVnebq_contextItem,.SVnebq_contextAction{transition:none}}.SVnebq_sentContext{color:var(--dsw-alias-label-secondary);margin:8px 0;font-size:13px}.SVnebq_sentContext summary{cursor:pointer}.SVnebq_sentContext pre,.SVnebq_sentContext blockquote{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit}.SVnebq_userContextHost{display:contents}[data-sidenote-context-replaced]{display:none!important}.SVnebq_sentChips{white-space:normal;display:inline}.SVnebq_sentChipGroup{display:inline}.SVnebq_sentChip{vertical-align:middle;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font:inherit;border:0;border-radius:6px;align-items:center;gap:4px;margin:0 6px 2px 0;padding:2px 6px;font-size:12px;line-height:18px;transition:background .14s,color .14s;display:inline-flex}button.SVnebq_sentChip{cursor:pointer}button.SVnebq_sentChip:hover,button.SVnebq_sentChip[aria-expanded=true]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover-solid)}button.SVnebq_sentChip:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.SVnebq_sentChevron,.SVnebq_sentChevronOpen{opacity:.65;flex:none;transition:transform .14s}.SVnebq_sentChevronOpen{transform:rotate(180deg)}.SVnebq_sentPreview{text-align:left;white-space:normal}@media (prefers-reduced-motion:reduce){.SVnebq_sentChip,.SVnebq_sentChevron,.SVnebq_sentChevronOpen{transition:none}}";
6387
+ const tagId = "dsh-sidenote/annotations.module.css";
6388
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
6389
+ const tag = document.createElement("style");
6390
+ tag.dataset.plugin = "dsh-sidenote";
6391
+ tag.dataset.pluginCss = tagId;
6392
+ tag.textContent = css;
6393
+ document.head.appendChild(tag);
6365
6394
  }
6366
6395
  var annotations_module_css_default = {
6396
+ "storageNotice": "SVnebq_storageNotice",
6397
+ "contextAction": "SVnebq_contextAction",
6398
+ "userContextHost": "SVnebq_userContextHost",
6399
+ "chipDismissShade": "SVnebq_chipDismissShade",
6400
+ "sentChipGroup": "SVnebq_sentChipGroup",
6401
+ "sentChip": "SVnebq_sentChip",
6402
+ "contextFooter": "SVnebq_contextFooter",
6403
+ "sentContext": "SVnebq_sentContext",
6404
+ "sentChevronOpen": "SVnebq_sentChevronOpen",
6405
+ "contextPanel": "SVnebq_contextPanel",
6406
+ "contextAppear": "SVnebq_contextAppear",
6407
+ "sentChevron": "SVnebq_sentChevron",
6408
+ "contextEditor": "SVnebq_contextEditor",
6409
+ "contextNumber": "SVnebq_contextNumber",
6410
+ "contextText": "SVnebq_contextText",
6411
+ "sentChips": "SVnebq_sentChips",
6412
+ "contextContent": "SVnebq_contextContent",
6413
+ "sentPreview": "SVnebq_sentPreview",
6414
+ "chipMain": "SVnebq_chipMain",
6415
+ "contextItem": "SVnebq_contextItem",
6416
+ "contextLabel": "SVnebq_contextLabel",
6367
6417
  "chipWrap": "SVnebq_chipWrap",
6418
+ "contextActions": "SVnebq_contextActions",
6419
+ "contextSource": "SVnebq_contextSource",
6368
6420
  "chipClearAll": "SVnebq_chipClearAll",
6369
- "sentContext": "SVnebq_sentContext",
6370
- "chipNumber": "SVnebq_chipNumber",
6371
- "chipRow": "SVnebq_chipRow",
6372
- "chipPanel": "SVnebq_chipPanel",
6373
- "chipRemove": "SVnebq_chipRemove",
6374
- "side": "SVnebq_side",
6375
- "storageNotice": "SVnebq_storageNotice",
6376
- "chipNote": "SVnebq_chipNote",
6377
6421
  "chip": "SVnebq_chip",
6378
- "chipText": "SVnebq_chipText"
6422
+ "chipDismiss": "SVnebq_chipDismiss",
6423
+ "contextList": "SVnebq_contextList"
6379
6424
  };
6380
6425
  //#endregion
6381
6426
  //#region src/client/annotations/chip.tsx
6382
- /**
6383
- * The 「N 条注释」 composer chip (Delivery_02 重构): a `conversation.input.dock`
6384
- * list entry (the official composer-attachment seat; todo dock lives at order
6385
- * 0, the queue strip at 20 — we sit between at 10). The chip counts the
6386
- * session's ACTIVE annotations and expands inline to preview/remove each one.
6387
- *
6388
- * 架构分工:chip 只呈现与增删注释对象;发送携带由 send.ts 拦截器负责
6389
- * (协议块在提交瞬间拼入,草稿不再被污染),sent 迁移也由拦截器显式触发
6390
- * ——chip 不再监听草稿发送沿。
6391
- */
6392
- function AnnotationChipInner(props) {
6427
+ /** Shared, compact context dock for both native composers. */
6428
+ function AnnotationDetail({ item, store, pending, onEdit }) {
6429
+ const [editing, setEditing] = (0, react.useState)(false);
6430
+ const [note, setNote] = (0, react.useState)(item.note);
6431
+ const cancelled = (0, react.useRef)(false);
6432
+ const save = () => {
6433
+ if (!cancelled.current) store.setNote(item.id, note);
6434
+ setEditing(false);
6435
+ };
6436
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
6437
+ className: annotations_module_css_default.contextItem,
6438
+ children: [
6439
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6440
+ className: annotations_module_css_default.contextNumber,
6441
+ children: [item.number, "."]
6442
+ }),
6443
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6444
+ className: annotations_module_css_default.contextContent,
6445
+ children: [
6446
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6447
+ className: annotations_module_css_default.contextLabel,
6448
+ children: t("selectedText")
6449
+ }),
6450
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6451
+ className: annotations_module_css_default.contextText,
6452
+ children: item.text
6453
+ }),
6454
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6455
+ className: annotations_module_css_default.contextLabel,
6456
+ children: t("userComment")
6457
+ }),
6458
+ editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
6459
+ autoFocus: true,
6460
+ rows: 3,
6461
+ className: annotations_module_css_default.contextEditor,
6462
+ "aria-label": t("userComment"),
6463
+ value: note,
6464
+ onChange: (event) => {
6465
+ setNote(event.target.value);
6466
+ },
6467
+ onBlur: save,
6468
+ onKeyDown: (event) => {
6469
+ if (event.key === "Escape") {
6470
+ event.preventDefault();
6471
+ event.stopPropagation();
6472
+ cancelled.current = true;
6473
+ setEditing(false);
6474
+ }
6475
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
6476
+ event.preventDefault();
6477
+ save();
6478
+ }
6479
+ }
6480
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6481
+ className: annotations_module_css_default.contextText,
6482
+ children: item.note || /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6483
+ className: annotations_module_css_default.contextLabel,
6484
+ children: t("noNote")
6485
+ })
6486
+ })
6487
+ ]
6488
+ }),
6489
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6490
+ className: annotations_module_css_default.contextActions,
6491
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6492
+ type: "button",
6493
+ className: annotations_module_css_default.contextAction,
6494
+ disabled: pending,
6495
+ "aria-label": t(editing ? "saveNoteAria" : "editNoteAria"),
6496
+ title: t(editing ? "saveNoteAria" : "editNoteAria"),
6497
+ onMouseDown: (event) => {
6498
+ if (editing) event.preventDefault();
6499
+ },
6500
+ onClick: () => {
6501
+ if (editing) save();
6502
+ else {
6503
+ cancelled.current = false;
6504
+ setNote(item.note);
6505
+ setEditing(true);
6506
+ onEdit();
6507
+ }
6508
+ },
6509
+ children: editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCheckOutline16, { size: 16 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconEditOutline16, { size: 16 })
6510
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6511
+ type: "button",
6512
+ className: annotations_module_css_default.contextAction,
6513
+ disabled: pending,
6514
+ "aria-label": t("removeAria", { n: item.number }),
6515
+ title: t("removeTitle"),
6516
+ onClick: () => {
6517
+ store.remove(item.id);
6518
+ },
6519
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconTrashOutline16, { size: 16 })
6520
+ })]
6521
+ })
6522
+ ]
6523
+ });
6524
+ }
6525
+ function ContextChips({ ctx, store, reflow, sessionId }) {
6393
6526
  useLocaleTick();
6394
- const { store, targetSessionId: sessionId } = props;
6395
- (0, react.useSyncExternalStore)((0, react.useCallback)((cb) => store.subscribe(cb), [store]), () => store.getSnapshot());
6396
- const [expanded, setExpanded] = (0, react.useState)(false);
6397
- const rootRef = (0, react.useRef)(null);
6527
+ (0, react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
6528
+ (0, react.useSyncExternalStore)(reflow.subscribe, reflow.getSnapshot);
6529
+ const diagnostics = hostDiagnostics(ctx);
6530
+ const capabilities = (0, react.useSyncExternalStore)(diagnostics.subscribe, diagnostics.getSnapshot);
6531
+ const [open, setOpen] = (0, react.useState)(null);
6532
+ const [position, setPosition] = (0, react.useState)({ visibility: "hidden" });
6533
+ const root = (0, react.useRef)(null);
6534
+ const panel = (0, react.useRef)(null);
6535
+ const timer = (0, react.useRef)(void 0);
6536
+ const id = (0, react.useId)();
6537
+ const clearTimer = () => {
6538
+ clearTimeout(timer.current);
6539
+ timer.current = void 0;
6540
+ };
6541
+ const active = store.listActive(sessionId);
6542
+ const reflows = reflow.list(sessionId);
6543
+ const submission = store.getSubmission(sessionId);
6544
+ const hasItems = open?.kind === "annotations" ? active.length > 0 : reflows.length > 0;
6545
+ const shown = open !== null && hasItems;
6546
+ const label = (kind) => kind === "annotations" ? t(active.length === 1 ? "chipOne" : "chipMany", { n: active.length }) : t(reflows.length === 1 ? "reflowChipOne" : "reflowChipMany", { n: reflows.length });
6398
6547
  (0, react.useEffect)(() => {
6399
- if (!expanded) return;
6400
- const onKeyDown = (event) => {
6401
- if (event.key === "Escape") {
6402
- event.stopPropagation();
6403
- setExpanded(false);
6548
+ setOpen(null);
6549
+ return clearTimer;
6550
+ }, [sessionId]);
6551
+ (0, react.useEffect)(() => {
6552
+ if (!submission.pending && submission.error === t("inputUnavailable") && resolveInput(ctx, sessionId)) store.setSubmission(sessionId, { pending: false });
6553
+ }, [capabilities, sessionId]);
6554
+ (0, react.useEffect)(() => {
6555
+ if (!hasItems) setOpen(null);
6556
+ }, [hasItems]);
6557
+ (0, react.useLayoutEffect)(() => {
6558
+ if (!shown) return;
6559
+ const place = () => {
6560
+ const rect = root.current.getBoundingClientRect();
6561
+ const width = Math.min(480, rect.width, window.innerWidth - 24);
6562
+ setPosition({
6563
+ width,
6564
+ left: Math.max(12, Math.min(rect.left, window.innerWidth - width - 12)),
6565
+ bottom: window.innerHeight - rect.top + 8,
6566
+ maxHeight: Math.min(280, window.innerHeight / 3, rect.top - 16)
6567
+ });
6568
+ };
6569
+ place();
6570
+ window.addEventListener("resize", place);
6571
+ window.addEventListener("scroll", place, true);
6572
+ return () => {
6573
+ window.removeEventListener("resize", place);
6574
+ window.removeEventListener("scroll", place, true);
6575
+ };
6576
+ }, [shown, open?.kind]);
6577
+ (0, react.useLayoutEffect)(() => {
6578
+ if (shown && open.pinned && root.current?.contains(document.activeElement)) panel.current?.focus();
6579
+ }, [
6580
+ shown,
6581
+ open?.kind,
6582
+ open?.pinned
6583
+ ]);
6584
+ (0, react.useEffect)(() => {
6585
+ if (!shown) return;
6586
+ const outside = (event) => {
6587
+ if (!root.current?.contains(event.target) && !panel.current?.contains(event.target)) {
6588
+ clearTimer();
6589
+ setOpen(null);
6404
6590
  }
6405
6591
  };
6406
- const onMouseDown = (event) => {
6407
- const root = rootRef.current;
6408
- if (root === null || !(event.target instanceof Node)) return;
6409
- if (!root.contains(event.target)) setExpanded(false);
6592
+ const escape = (event) => {
6593
+ if (event.key !== "Escape") return;
6594
+ event.preventDefault();
6595
+ clearTimer();
6596
+ setOpen(null);
6597
+ root.current?.querySelector(`[data-context-kind="${open?.kind}"]`)?.focus();
6410
6598
  };
6411
- document.addEventListener("keydown", onKeyDown, true);
6412
- document.addEventListener("mousedown", onMouseDown, true);
6599
+ document.addEventListener("pointerdown", outside);
6600
+ document.addEventListener("keydown", escape);
6413
6601
  return () => {
6414
- document.removeEventListener("keydown", onKeyDown, true);
6415
- document.removeEventListener("mousedown", onMouseDown, true);
6602
+ document.removeEventListener("pointerdown", outside);
6603
+ document.removeEventListener("keydown", escape);
6416
6604
  };
6417
- }, [expanded]);
6418
- (0, react.useEffect)(() => {
6419
- setExpanded(false);
6420
- }, [sessionId]);
6421
- const active = store.listActive(sessionId);
6422
- const submission = store.getSubmission(sessionId);
6423
- if (active.length === 0 && submission.error === void 0) return null;
6605
+ }, [shown, open?.kind]);
6606
+ const leave = () => {
6607
+ clearTimer();
6608
+ if (!open?.pinned) timer.current = setTimeout(() => {
6609
+ if (!panel.current?.contains(document.activeElement)) setOpen(null);
6610
+ }, 220);
6611
+ };
6612
+ const clear = (kind) => {
6613
+ clearTimer();
6614
+ if (kind === "annotations") for (const item of active) store.remove(item.id);
6615
+ else reflow.clearSession(sessionId);
6616
+ if (open?.kind === kind) setOpen(null);
6617
+ };
6618
+ const chip = (kind) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6619
+ className: annotations_module_css_default.chip,
6620
+ "data-open": shown && open.kind === kind || void 0,
6621
+ onPointerEnter: (event) => {
6622
+ if (event.pointerType === "touch") return;
6623
+ clearTimer();
6624
+ if (!open?.pinned) timer.current = setTimeout(() => {
6625
+ setOpen({
6626
+ kind,
6627
+ pinned: false
6628
+ });
6629
+ }, 160);
6630
+ },
6631
+ onPointerLeave: leave,
6632
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6633
+ type: "button",
6634
+ className: annotations_module_css_default.chipMain,
6635
+ "data-context-kind": kind,
6636
+ "aria-haspopup": "dialog",
6637
+ "aria-expanded": shown && open.kind === kind,
6638
+ "aria-controls": id,
6639
+ onClick: () => {
6640
+ clearTimer();
6641
+ setOpen(open?.kind === kind && open.pinned ? null : {
6642
+ kind,
6643
+ pinned: true
6644
+ });
6645
+ },
6646
+ children: [kind === "annotations" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconListPenOutline16, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconShareOutline16, { size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label(kind) })]
6647
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6648
+ className: annotations_module_css_default.chipDismissShade,
6649
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6650
+ type: "button",
6651
+ className: annotations_module_css_default.chipDismiss,
6652
+ disabled: submission.pending,
6653
+ "aria-label": t(kind === "annotations" ? "clearAnnotations" : "clearReflows"),
6654
+ title: t(kind === "annotations" ? "clearAnnotations" : "clearReflows"),
6655
+ onClick: () => {
6656
+ clear(kind);
6657
+ },
6658
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCloseOutline16, { size: 14 })
6659
+ })
6660
+ })]
6661
+ });
6662
+ if (!active.length && !reflows.length && !submission.error) return null;
6424
6663
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6425
- ref: rootRef,
6426
- className: [annotations_module_css_default.chipWrap, props.side ? annotations_module_css_default.side : ""].join(" "),
6427
- "data-sidenote-annotation-ui": "",
6664
+ ref: root,
6665
+ className: annotations_module_css_default.chipWrap,
6666
+ "data-sidenote-context-dock": true,
6667
+ "data-sidenote-annotation-ui": true,
6428
6668
  children: [
6669
+ active.length > 0 && chip("annotations"),
6670
+ reflows.length > 0 && chip("reflows"),
6429
6671
  store.getStorageStatus(sessionId) === "memory" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6430
6672
  className: annotations_module_css_default.storageNotice,
6431
6673
  role: "status",
6432
6674
  children: t("annotationStorageMemory")
6433
6675
  }),
6434
- submission.error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6676
+ submission.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6435
6677
  className: annotations_module_css_default.storageNotice,
6436
6678
  role: "alert",
6437
6679
  children: submission.error
6438
6680
  }),
6439
- active.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6440
- type: "button",
6441
- className: annotations_module_css_default.chip,
6442
- "aria-expanded": expanded,
6443
- onClick: () => {
6444
- setExpanded((open) => !open);
6445
- },
6446
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconListPenOutline16, { size: 12 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(active.length === 1 ? "chipOne" : "chipMany", { n: active.length }) })]
6447
- }),
6448
- active.length > 0 && props.onSend && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6449
- type: "button",
6450
- className: annotations_module_css_default.chip,
6451
- disabled: submission.pending || props.blocked,
6452
- onClick: props.onSend,
6453
- children: t("sendAnnotations")
6454
- }),
6455
- expanded && active.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
6456
- className: annotations_module_css_default.chipPanel,
6457
- children: [active.map((annotation) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
6458
- className: annotations_module_css_default.chipRow,
6459
- children: [
6460
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6461
- className: annotations_module_css_default.chipNumber,
6462
- children: annotation.number
6463
- }),
6464
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6465
- className: annotations_module_css_default.chipText,
6466
- title: annotation.note === "" ? annotation.text : annotation.text + "\n" + t("chipNote", { note: annotation.note }),
6467
- children: [annotation.text, annotation.note !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6468
- className: annotations_module_css_default.chipNote,
6469
- children: t("chipNote", { note: annotation.note })
6470
- })]
6471
- }),
6472
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6473
- type: "button",
6474
- className: annotations_module_css_default.chipRemove,
6475
- title: t("removeTitle"),
6476
- "aria-label": t("removeAria", { n: annotation.number }),
6477
- disabled: submission.pending,
6478
- onClick: () => {
6479
- store.remove(annotation.id);
6480
- },
6481
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCloseOutline16, { size: 12 })
6482
- })
6483
- ]
6484
- }, annotation.id)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
6485
- className: annotations_module_css_default.chipRow,
6681
+ shown && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6682
+ ref: panel,
6683
+ id,
6684
+ role: "dialog",
6685
+ tabIndex: -1,
6686
+ "aria-label": label(open.kind),
6687
+ className: annotations_module_css_default.contextPanel,
6688
+ style: position,
6689
+ "data-sidenote-context-preview": true,
6690
+ "data-sidenote-no-quote": true,
6691
+ onPointerEnter: clearTimer,
6692
+ onPointerLeave: leave,
6693
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
6694
+ className: annotations_module_css_default.contextList,
6695
+ children: open.kind === "annotations" ? active.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationDetail, {
6696
+ item,
6697
+ store,
6698
+ pending: submission.pending,
6699
+ onEdit: () => {
6700
+ setOpen({
6701
+ kind: "annotations",
6702
+ pinned: true
6703
+ });
6704
+ }
6705
+ }, item.id)) : reflows.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
6706
+ className: annotations_module_css_default.contextItem,
6707
+ children: [
6708
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6709
+ className: annotations_module_css_default.contextNumber,
6710
+ children: [index + 1, "."]
6711
+ }),
6712
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6713
+ className: annotations_module_css_default.contextContent,
6714
+ children: [
6715
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6716
+ className: annotations_module_css_default.contextSource,
6717
+ children: item.sideTitle
6718
+ }),
6719
+ item.question && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6720
+ className: annotations_module_css_default.contextLabel,
6721
+ children: t("contextrequest")
6722
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6723
+ className: annotations_module_css_default.contextText,
6724
+ children: item.question
6725
+ })] }),
6726
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6727
+ className: annotations_module_css_default.contextLabel,
6728
+ children: t("contextreply")
6729
+ }),
6730
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6731
+ className: annotations_module_css_default.contextText,
6732
+ children: item.text
6733
+ })
6734
+ ]
6735
+ }),
6736
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6737
+ className: annotations_module_css_default.contextActions,
6738
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6739
+ type: "button",
6740
+ className: annotations_module_css_default.contextAction,
6741
+ disabled: submission.pending,
6742
+ "aria-label": t("reflowRemoveTitle"),
6743
+ title: t("reflowRemoveTitle"),
6744
+ onClick: () => {
6745
+ reflow.remove(item.id);
6746
+ },
6747
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconTrashOutline16, { size: 16 })
6748
+ })
6749
+ })
6750
+ ]
6751
+ }, item.id))
6752
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6753
+ className: annotations_module_css_default.contextFooter,
6486
6754
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6487
6755
  type: "button",
6488
6756
  className: annotations_module_css_default.chipClearAll,
6489
6757
  disabled: submission.pending,
6490
6758
  onClick: () => {
6491
- for (const a of active) store.remove(a.id);
6759
+ clear(open.kind);
6492
6760
  },
6493
6761
  children: t("clearAll")
6494
6762
  })
6495
6763
  })]
6496
- })
6764
+ }), document.body)
6497
6765
  ]
6498
6766
  });
6499
6767
  }
6500
- function AnnotationChip(props) {
6501
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationBoundary, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationChipInner, { ...props }) });
6502
- }
6503
- /** The same native dock entry is rendered in main and embedded conversations. */
6504
- function createAnnotationChip(ctx, store, reflow) {
6505
- return function AnnotationChipEntry(props) {
6506
- const diagnostics = hostDiagnostics(ctx);
6507
- const capabilities = (0, react.useSyncExternalStore)(diagnostics.subscribe, diagnostics.getSnapshot);
6508
- (0, react.useEffect)(() => {
6509
- const state = store.getSubmission(props.session.sessionId);
6510
- if (!state.pending && state.error === t("inputUnavailable") && resolveInput(ctx, props.session.sessionId)) store.setSubmission(props.session.sessionId, { pending: false });
6511
- }, [capabilities, props.session.sessionId]);
6512
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationChip, {
6768
+ function createContextChips(ctx, store, reflow) {
6769
+ return function ContextChipsEntry(props) {
6770
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationBoundary, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextChips, {
6771
+ ctx,
6513
6772
  store,
6514
- targetSessionId: props.session.sessionId,
6515
- blocked: props.input.phase !== "plain" || props.input.draft.trimStart().startsWith("/"),
6516
- onSend: () => {
6517
- submitControlledInput(ctx, store, reflow, props.session.sessionId);
6518
- }
6519
- });
6773
+ reflow,
6774
+ sessionId: props.session.sessionId
6775
+ }, props.session.sessionId) });
6520
6776
  };
6521
6777
  }
6522
6778
  //#endregion
6523
- //#region src/client/annotate/chip.tsx
6524
- /** 「侧边回流」chip(dock order 11):主会话待回流对象的可预览/可移除入口。 */
6525
- function createReflowChip(reflow) {
6526
- function ReflowChip(props) {
6527
- useLocaleTick();
6528
- const sessionId = props.session.sessionId;
6529
- (0, react.useSyncExternalStore)((0, react.useCallback)((cb) => reflow.subscribe(cb), [reflow]), () => reflow.getSnapshot());
6530
- const [expanded, setExpanded] = (0, react.useState)(false);
6531
- const rootRef = (0, react.useRef)(null);
6532
- (0, react.useEffect)(() => {
6533
- if (!expanded) return;
6534
- const onKeyDown = (event) => {
6535
- if (event.key === "Escape") {
6536
- event.stopPropagation();
6537
- setExpanded(false);
6538
- }
6539
- };
6540
- const onMouseDown = (event) => {
6541
- const root = rootRef.current;
6542
- if (root === null || !(event.target instanceof Node)) return;
6543
- if (!root.contains(event.target)) setExpanded(false);
6544
- };
6545
- document.addEventListener("keydown", onKeyDown, true);
6546
- document.addEventListener("mousedown", onMouseDown, true);
6547
- return () => {
6548
- document.removeEventListener("keydown", onKeyDown, true);
6549
- document.removeEventListener("mousedown", onMouseDown, true);
6550
- };
6551
- }, [expanded]);
6552
- (0, react.useEffect)(() => {
6553
- setExpanded(false);
6554
- }, [sessionId]);
6555
- const items = reflow.list(sessionId);
6556
- if (items.length === 0) return null;
6557
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6558
- ref: rootRef,
6559
- className: annotations_module_css_default.chipWrap,
6560
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6561
- type: "button",
6562
- className: annotations_module_css_default.chip,
6563
- "aria-expanded": expanded,
6564
- onClick: () => {
6565
- setExpanded((open) => !open);
6566
- },
6567
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconShareOutline16, { size: 12 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(items.length === 1 ? "reflowChipOne" : "reflowChipMany", { n: items.length }) })]
6568
- }), expanded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
6569
- className: annotations_module_css_default.chipPanel,
6570
- children: items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
6571
- className: annotations_module_css_default.chipRow,
6572
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6573
- className: annotations_module_css_default.chipText,
6574
- title: item.text,
6575
- children: [
6576
- t("reflowFrom", { title: item.sideTitle }),
6577
- " ",
6578
- item.text.replace(/[*_`#>[\]]/g, "").replace(/\s+/g, " ")
6579
- ]
6580
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6581
- type: "button",
6582
- className: annotations_module_css_default.chipRemove,
6583
- title: t("reflowRemoveTitle"),
6584
- "aria-label": t("reflowRemoveTitle"),
6585
- onClick: () => {
6586
- reflow.remove(item.id);
6587
- },
6588
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCloseOutline16, { size: 12 })
6589
- })]
6590
- }, item.id))
6591
- })]
6779
+ //#region src/client/annotations/send.ts
6780
+ /** Main and side composers use this same native transaction and acceptance boundary. */
6781
+ async function submitAnnotations(options) {
6782
+ const { ctx, store, sessionId, input, annotations } = options;
6783
+ cancelInputRollback(input);
6784
+ const parts = [...options.leadingBlocks ?? []];
6785
+ if (annotations.length > 0) parts.push(buildProtocolBlock(annotations));
6786
+ const block = parts.join("\n\n");
6787
+ if (block === "") return false;
6788
+ const draftBeforeWait = input.state.getSnapshot();
6789
+ let prefix = "";
6790
+ let receipt;
6791
+ try {
6792
+ receipt = await prepareSubmission(ctx, sessionId, input, block);
6793
+ const draft = input.state.getSnapshot();
6794
+ if (draft.draft !== draftBeforeWait.draft || draft.draftRev !== draftBeforeWait.draftRev) throw new Error("等待会话就绪期间草稿已改变;正文和注释已保留,请重新发送");
6795
+ prefix = block + (draft.draft === "" ? "" : "\n\n");
6796
+ if (!prependInputText(input, prefix)) throw new Error("宿主输入尚未就绪,无法安全加入引用;内容已保留");
6797
+ receipt.submit(options.mode);
6798
+ await receipt.outcome;
6799
+ store.markSent(annotations.filter((annotation) => store.get(annotation.id) === annotation).map((annotation) => annotation.id));
6800
+ return true;
6801
+ } catch (error) {
6802
+ rollbackInputPrefix(input, prefix);
6803
+ const text = error instanceof Error ? error.message : String(error);
6804
+ store.setSubmission(sessionId, {
6805
+ pending: false,
6806
+ error: text
6592
6807
  });
6808
+ try {
6809
+ if (typeof input.notify === "function") input.notify("error", text);
6810
+ } catch {}
6811
+ return false;
6812
+ } finally {
6813
+ receipt?.dispose();
6593
6814
  }
6594
- return function ReflowChipEntry(props) {
6595
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationBoundary, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReflowChip, { ...props }) });
6596
- };
6597
6815
  }
6598
- //#endregion
6599
- //#region src/client/annotate/send.ts
6600
- function installSendInterceptor(ctx, store, reflow) {
6601
- /** 重入/连按护栏:事务进行中吞掉命中识别面的 Enter/点击(不重复驱动)。 */
6602
- const committingSessions = /* @__PURE__ */ new Set();
6603
- /**
6604
- * 手势尾随 click 的一次性吞并。pointerdown 提交后,同一次点击的 click 仍会
6605
- * 到达,而此时宿主主按钮已按 `primaryStops = running && empty` 变身「停止」
6606
- * ——放行它会立刻 `stop()` 掉刚发起的这一轮(表现:assistant/attempt 空流、
6607
- * 缺 turn/end、且没有任何错误提示)。只吞这一发,并带兜底超时。
6608
- */
6609
- let swallowNextClick = false;
6610
- let swallowTimer = 0;
6611
- const clearSwallow = () => {
6612
- swallowNextClick = false;
6613
- window.clearTimeout(swallowTimer);
6614
- };
6615
- const armSwallow = () => {
6616
- swallowNextClick = true;
6617
- window.clearTimeout(swallowTimer);
6618
- swallowTimer = window.setTimeout(clearSwallow, 2e3);
6619
- };
6620
- const currentSessionId = (seat) => seat ? composerSessionId(seat, currentSessionIdOf(ctx)) : currentSessionIdOf(ctx);
6621
- const hijack = (seat, mode) => {
6622
- const sessionId = currentSessionId(seat);
6623
- if (sessionId === "") return false;
6624
- const active = store.listActive(sessionId);
6625
- const reflows = reflow.list(sessionId);
6626
- const prefix = submissionExtrasOf(sessionId)?.firstSendPrefix();
6627
- if (active.length === 0 && reflows.length === 0 && !prefix) return false;
6628
- const input = resolveInput(ctx, sessionId);
6629
- if (input === void 0) {
6630
- store.setSubmission(sessionId, {
6631
- pending: false,
6632
- error: t("inputUnavailable")
6633
- });
6816
+ /** Shared by native input gestures and the explicit annotation/reflow actions. */
6817
+ async function submitControlledInput(ctx, store, reflow, sessionId, mode) {
6818
+ if (store.getSubmission(sessionId).pending) return false;
6819
+ const input = resolveInput(ctx, sessionId);
6820
+ if (!input) {
6821
+ store.setSubmission(sessionId, {
6822
+ pending: false,
6823
+ error: t("inputUnavailable")
6824
+ });
6825
+ return false;
6826
+ }
6827
+ const draft = input.state.getSnapshot();
6828
+ if (draft.phase !== "plain" || draft.draft.trimStart().startsWith("/")) return false;
6829
+ const active = store.listActive(sessionId);
6830
+ const reflows = reflow.list(sessionId);
6831
+ if (draft.draft.trim() === "" && inputAttachmentIds(input).length === 0 && active.length === 0 && reflows.length === 0) return false;
6832
+ const extras = submissionExtrasOf(sessionId);
6833
+ const prefix = extras?.firstSendPrefix();
6834
+ store.setSubmission(sessionId, { pending: true });
6835
+ try {
6836
+ const { injectedIds } = await tryInjectReflows(sessionId, reflows);
6837
+ for (const id of injectedIds) reflow.remove(id);
6838
+ const hitchhikers = reflows.filter((item) => !injectedIds.has(item.id));
6839
+ if (active.length === 0 && hitchhikers.length === 0 && !prefix) {
6840
+ if (input.state.getSnapshot().draft.trim() !== "" || inputAttachmentIds(input).length > 0) input.submit(mode);
6634
6841
  return true;
6635
6842
  }
6636
- const snap = input.state.getSnapshot();
6637
- const draft = snap.draft;
6638
- if (active.length === 0 && reflows.length === 0 && draft.trim() === "" && inputAttachmentIds(input).length === 0) return false;
6639
- if (draft.trimStart().startsWith("/")) return false;
6640
- if (snap.phase !== "plain") return false;
6641
- if (composerHasMenu(seat)) return false;
6642
- committingSessions.add(sessionId);
6643
- submitControlledInput(ctx, store, reflow, sessionId, mode).finally(() => {
6644
- committingSessions.delete(sessionId);
6843
+ const accepted = await submitAnnotations({
6844
+ ctx,
6845
+ store,
6846
+ sessionId,
6847
+ input,
6848
+ annotations: active,
6849
+ leadingBlocks: [...prefix ? [prefix] : [], ...hitchhikers.map(buildReflowBlock)],
6850
+ mode
6645
6851
  });
6646
- return true;
6647
- };
6648
- const onKeyDown = (event) => {
6649
- const target = event.target;
6650
- if (!isMainComposerInput(target)) return;
6651
- const seat = mainComposerOf(target);
6652
- if (event.isComposing || event.keyCode === 229) return;
6653
- if (committingSessions.has(currentSessionId(seat)) || store.getSubmission(currentSessionId(seat)).pending) {
6654
- if (event.key === "Enter") {
6655
- event.preventDefault();
6656
- event.stopImmediatePropagation();
6657
- }
6658
- return;
6659
- }
6660
- if (event.key !== "Enter" || event.shiftKey) return;
6661
- if (!hijack(seat, event.metaKey || event.ctrlKey ? "steer" : void 0)) return;
6662
- event.preventDefault();
6663
- event.stopImmediatePropagation();
6664
- };
6665
- const onClick = (event) => {
6666
- const target = event.target;
6667
- if (!(target instanceof Element)) return;
6668
- const seat = mainComposerOf(target);
6669
- if (seat === null) return;
6670
- const button = target.closest("button");
6671
- if (swallowNextClick && button === findSendButtonInCard(seat)) {
6672
- clearSwallow();
6673
- event.preventDefault();
6674
- event.stopImmediatePropagation();
6675
- return;
6676
- }
6677
- if (button === null) return;
6678
- if (button !== findSendButtonInCard(seat)) return;
6679
- if (ctx.sessions.binding(currentSessionId(seat))?.session.getSnapshot().running === true && resolveInput(ctx, currentSessionId(seat))?.state.getSnapshot().draft.trim() === "") return;
6680
- if (committingSessions.has(currentSessionId(seat))) {
6681
- event.preventDefault();
6682
- event.stopImmediatePropagation();
6683
- return;
6852
+ if (accepted) {
6853
+ for (const item of hitchhikers) reflow.remove(item.id);
6854
+ extras?.onSubmitText(draft.draft);
6855
+ extras?.onAccepted();
6684
6856
  }
6685
- if (!hijack(seat)) return;
6686
- event.preventDefault();
6687
- event.stopImmediatePropagation();
6688
- };
6689
- /**
6690
- * 空草稿补位:只接管「因空草稿而 disabled」的主按钮。enabled 时立即放行,
6691
- * 正常路径仍由上面的 click 拦截负责 —— 二者以 button.disabled 互斥。
6692
- */
6693
- const onPointerDown = (event) => {
6694
- if (event.button !== 0) return;
6695
- const target = event.target;
6696
- if (!(target instanceof Element)) return;
6697
- const seat = mainComposerOf(target);
6698
- if (seat === null) return;
6699
- const button = target.closest("button");
6700
- if (!(button instanceof HTMLButtonElement)) return;
6701
- if (!button.disabled) return;
6702
- if (button !== findSendButtonInCard(seat)) return;
6703
- if (committingSessions.has(currentSessionId(seat))) return;
6704
- const sessionId = currentSessionId(seat);
6705
- if (sessionId === "") return;
6706
- const input = resolveInput(ctx, sessionId);
6707
- if (input === void 0 || input.state.getSnapshot().draft.trim() !== "") return;
6708
- if (!hijack(seat)) return;
6709
- armSwallow();
6710
- event.preventDefault();
6711
- event.stopImmediatePropagation();
6712
- };
6713
- document.addEventListener("keydown", onKeyDown, true);
6714
- document.addEventListener("click", onClick, true);
6715
- document.addEventListener("pointerdown", onPointerDown, true);
6716
- return () => {
6717
- clearSwallow();
6718
- document.removeEventListener("keydown", onKeyDown, true);
6719
- document.removeEventListener("click", onClick, true);
6720
- document.removeEventListener("pointerdown", onPointerDown, true);
6721
- };
6857
+ return accepted;
6858
+ } catch (error) {
6859
+ store.setSubmission(sessionId, {
6860
+ pending: false,
6861
+ error: error instanceof Error ? error.message : String(error)
6862
+ });
6863
+ return false;
6864
+ } finally {
6865
+ store.setSubmission(sessionId, {
6866
+ ...store.getSubmission(sessionId),
6867
+ pending: false
6868
+ });
6869
+ }
6722
6870
  }
6723
6871
  //#endregion
6724
- //#region src/client/annotations/NativeUserContext.tsx
6725
- /** Present controlled context without editing React-owned DOM or durable data.
6726
- * The host still owns the user bubble, images, reference nodes and actions. */
6727
- function registerUserContext(ctx) {
6872
+ //#region src/client/annotations/NativeComposer.tsx
6873
+ /** The host has already resolved Enter/click, menus, IME and busy delivery mode. */
6874
+ function submitNativeWithContext(ctx, annotations, reflow, sessionId, native, mode, source) {
6875
+ if (annotations.getSubmission(sessionId).pending) return;
6876
+ const hasContext = annotations.countActive(sessionId) > 0 || reflow.list(sessionId).length > 0;
6877
+ const input = resolveInput(ctx, sessionId);
6878
+ if (!input) {
6879
+ if (hasContext || submissionExtrasOf(sessionId)?.firstSendPrefix()) annotations.setSubmission(sessionId, {
6880
+ pending: false,
6881
+ error: t("inputUnavailable")
6882
+ });
6883
+ else native(mode, source);
6884
+ return;
6885
+ }
6886
+ const draft = input.state.getSnapshot();
6887
+ if (draft.phase !== "plain" || draft.draft.trimStart().startsWith("/")) {
6888
+ native(mode, source);
6889
+ return;
6890
+ }
6891
+ const hasMessage = draft.draft.trim() !== "" || inputAttachmentIds(input).length > 0;
6892
+ if (hasContext || hasMessage && submissionExtrasOf(sessionId)?.firstSendPrefix()) submitControlledInput(ctx, annotations, reflow, sessionId, mode);
6893
+ else native(mode, source);
6894
+ }
6895
+ function ContextComposer({ Original, props, ctx, annotations, reflow }) {
6896
+ (0, react.useSyncExternalStore)(annotations.subscribe, annotations.getSnapshot);
6897
+ (0, react.useSyncExternalStore)(reflow.subscribe, reflow.getSnapshot);
6898
+ const input = props.useInput((state) => state);
6899
+ const session = props.useSession((state) => state);
6900
+ const busyMode = props.useBusyEnter((mode) => mode);
6901
+ const sessionId = props.sessionId ?? "";
6902
+ const pending = annotations.getSubmission(sessionId).pending;
6903
+ const contextOnly = (annotations.countActive(sessionId) > 0 || reflow.list(sessionId).length > 0) && input?.draft.trim() === "" && (input.attachmentIds ?? input.imageIds ?? []).length === 0;
6904
+ const root = (0, react.useRef)(null);
6905
+ const [seat] = (0, react.useState)(() => document.createElement("span"));
6906
+ const [nativeButton, setNativeButton] = (0, react.useState)(null);
6907
+ const keyboard = (0, react.useMemo)(() => props.keyboard && new Proxy(props.keyboard, { get(target, property) {
6908
+ if (property === "submit") return (mode, source) => submitNativeWithContext(ctx, annotations, reflow, sessionId, target.submit.bind(target), mode, source);
6909
+ const value = Reflect.get(target, property, target);
6910
+ return typeof value === "function" ? value.bind(target) : value;
6911
+ } }), [
6912
+ props.keyboard,
6913
+ ctx,
6914
+ annotations,
6915
+ reflow,
6916
+ sessionId
6917
+ ]);
6918
+ const useInput = (0, react.useCallback)((select) => props.useInput((state) => select(pending && state?.phase === "plain" ? {
6919
+ ...state,
6920
+ phase: "submitting"
6921
+ } : state)), [props.useInput, pending]);
6922
+ (0, react.useLayoutEffect)(() => {
6923
+ if (!contextOnly || !root.current) return;
6924
+ const button = findSendButtonInCard(root.current);
6925
+ if (!button) return;
6926
+ setNativeButton(button);
6927
+ seat.style.display = "contents";
6928
+ button.parentElement.append(seat);
6929
+ if (!session?.running) button.dataset.sidenoteContextReplaced = "";
6930
+ return () => {
6931
+ delete button.dataset.sidenoteContextReplaced;
6932
+ seat.remove();
6933
+ };
6934
+ }, [
6935
+ contextOnly,
6936
+ session?.running,
6937
+ sessionId,
6938
+ props.variant,
6939
+ seat
6940
+ ]);
6941
+ const offline = session?.subagent?.address.mode === "continuable" && session.subagent.parentAvailable !== true;
6942
+ const disabled = pending || input?.phase !== "plain" || props.disabled || props.blocked !== void 0 || session?.removed || offline || !keyboard || !props.inputActions;
6943
+ const mode = session?.running ? busyMode : void 0;
6944
+ const label = props.t(session?.running ? mode === "steer" ? "input.send.steer" : "input.send.queue" : "input.send");
6945
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6946
+ className: annotations_module_css_default.userContextHost,
6947
+ ref: root,
6948
+ children: [(0, react.createElement)(Original, {
6949
+ ...props,
6950
+ keyboard,
6951
+ useInput
6952
+ }), contextOnly && nativeButton && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6953
+ type: "button",
6954
+ "data-sidenote-submit": true,
6955
+ className: nativeButton.className,
6956
+ disabled,
6957
+ "aria-label": label,
6958
+ title: label,
6959
+ onClick: () => {
6960
+ keyboard?.submit(mode, "click");
6961
+ },
6962
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
6963
+ width: "20",
6964
+ height: "20",
6965
+ viewBox: "0 0 24 24",
6966
+ fill: "none",
6967
+ stroke: "currentColor",
6968
+ strokeWidth: "2",
6969
+ strokeLinecap: "round",
6970
+ strokeLinejoin: "round",
6971
+ "aria-hidden": "true",
6972
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 19V5m-6 6 6-6 6 6" })
6973
+ })
6974
+ }), seat)]
6975
+ });
6976
+ }
6977
+ /** Keep the native editor and controls; replace only its empty submit seat
6978
+ * while plugin-owned context exists, and retain a separate native Stop. */
6979
+ function registerNativeComposer(ctx, annotations, reflow) {
6728
6980
  const slots = ctx.slots;
6729
- ctx.slots.inject("conversation.chat.node", () => {
6981
+ ctx.slots.inject("conversation.composer.bar", () => {
6730
6982
  const patched = /* @__PURE__ */ new Map();
6731
6983
  const patch = () => {
6732
- for (const entry of slots.entries("conversation.chat.node")) {
6733
- if (!["user", "steering"].includes(entry.options.key ?? "") || patched.has(entry)) continue;
6984
+ for (const entry of slots.entries("conversation.composer.bar")) {
6985
+ if (patched.has(entry)) continue;
6734
6986
  const Original = entry.component;
6735
- function ContextUser(props) {
6736
- const blocks = props.node.data.content;
6737
- const at = blocks.findIndex((block) => block.type === "text");
6738
- const text = blocks[at]?.text ?? "";
6739
- const snapshot = splitSnapshotPrefix(text);
6740
- const afterSnapshot = snapshot?.rest ?? text;
6741
- const protocol = splitProtocolPrefix(afterSnapshot);
6742
- if (!snapshot && !protocol) return (0, react.createElement)(Original, props);
6743
- const content = blocks.map((block, index) => index === at ? {
6744
- ...block,
6745
- text: afterSnapshot.slice(protocol?.length ?? 0).trimStart()
6746
- } : block);
6747
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6748
- snapshot && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6749
- className: annotations_module_css_default.sentContext,
6750
- "data-sidenote-progress-snapshot": true,
6751
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("snapshotContext") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: snapshot.snapshot })]
6752
- }),
6753
- protocol && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6754
- "data-dsh-sidenote-bubble": true,
6755
- children: [protocol.annotations.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6756
- className: annotations_module_css_default.sentContext,
6757
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("sentChipLabel", { n: protocol.annotations.length }) }), protocol.annotations.map((annotation) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("blockquote", { children: [annotation.quote, annotation.note && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: annotation.note })] }, annotation.id))]
6758
- }), protocol.reflows.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
6759
- className: annotations_module_css_default.sentContext,
6760
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("reflowBubbleLabel") }), protocol.reflows.map((reflow, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("blockquote", { children: [reflow.source, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: flattenReflowContent(reflow.content) })] }, index))]
6761
- })]
6762
- }),
6763
- (0, react.createElement)(Original, {
6764
- ...props,
6765
- node: {
6766
- ...props.node,
6767
- data: {
6768
- ...props.node.data,
6769
- content
6770
- }
6771
- }
6772
- })
6773
- ] });
6774
- }
6775
- entry.component = ContextUser;
6987
+ const Wrapped = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextComposer, {
6988
+ Original,
6989
+ props,
6990
+ ctx,
6991
+ annotations,
6992
+ reflow
6993
+ });
6994
+ entry.component = Wrapped;
6776
6995
  patched.set(entry, {
6777
6996
  original: Original,
6778
- wrapped: ContextUser
6997
+ wrapped: Wrapped
6779
6998
  });
6780
6999
  }
6781
7000
  };
6782
7001
  patch();
6783
- const off = slots.subscribe("conversation.chat.node", patch);
7002
+ const off = slots.subscribe("conversation.composer.bar", patch);
6784
7003
  return () => {
6785
7004
  off();
6786
7005
  for (const [entry, value] of patched) if (entry.component === value.wrapped) entry.component = value.original;
@@ -6788,6 +7007,320 @@ ${entries.map((entry) => {
6788
7007
  });
6789
7008
  }
6790
7009
  //#endregion
7010
+ //#region src/client/annotations/NativeUserContext.tsx
7011
+ const CONTEXT_NODE = "dsh-sidenote-context";
7012
+ /** Injected notices and their following human input are one admitted batch.
7013
+ * Project its reflows into that bubble without changing or resending history. */
7014
+ function injectedReflowPrefix(nodes, seq) {
7015
+ const prefixes = [];
7016
+ for (let i = nodes.findIndex((node) => node.seq === seq) - 1; i >= 0; i--) {
7017
+ const node = nodes[i];
7018
+ if (node.kind !== "context") break;
7019
+ if (node.source?.kind !== "dsh-sidenote") continue;
7020
+ const text = (node.content ?? []).filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n");
7021
+ const start = text.indexOf("<reflow ");
7022
+ if (start < 0) continue;
7023
+ const prefix = splitProtocolPrefix(text.slice(start));
7024
+ if (prefix?.reflows.length) prefixes.unshift(text.slice(start, start + prefix.length));
7025
+ }
7026
+ return prefixes.join("\n\n");
7027
+ }
7028
+ /** One prefix node leaves the body, @ references and attachments to the host. */
7029
+ const sentContextRule = {
7030
+ id: CONTEXT_NODE,
7031
+ order: 0,
7032
+ match(text, from) {
7033
+ const snapshot = splitSnapshotPrefix(text);
7034
+ const start = snapshot ? text.length - snapshot.rest.length : 0;
7035
+ if (from > start) return void 0;
7036
+ const protocol = splitProtocolPrefix(text.slice(start));
7037
+ return protocol ? {
7038
+ id: CONTEXT_NODE,
7039
+ start,
7040
+ end: start + protocol.length,
7041
+ data: protocol
7042
+ } : void 0;
7043
+ }
7044
+ };
7045
+ function ContextChip({ label, icon, children, interactive }) {
7046
+ const [open, setOpen] = (0, react.useState)(null);
7047
+ const [position, setPosition] = (0, react.useState)({ visibility: "hidden" });
7048
+ const anchor = (0, react.useRef)(null);
7049
+ const panel = (0, react.useRef)(null);
7050
+ const timer = (0, react.useRef)(void 0);
7051
+ const clearTimer = () => {
7052
+ clearTimeout(timer.current);
7053
+ timer.current = void 0;
7054
+ };
7055
+ const leave = () => {
7056
+ clearTimer();
7057
+ if (!open?.pinned) timer.current = setTimeout(() => {
7058
+ if (!panel.current?.contains(document.activeElement)) setOpen(null);
7059
+ }, 160);
7060
+ };
7061
+ const id = (0, react.useId)();
7062
+ const expanded = open !== null;
7063
+ (0, react.useEffect)(() => clearTimer, []);
7064
+ (0, react.useLayoutEffect)(() => {
7065
+ if (!expanded) return;
7066
+ const place = () => {
7067
+ const rect = anchor.current.getBoundingClientRect();
7068
+ const view = conversationViewOf(anchor.current).getBoundingClientRect();
7069
+ const left = Math.max(12, view.left + 12), right = Math.min(window.innerWidth - 12, view.right - 12);
7070
+ const top = Math.max(12, view.top + 12), bottom = Math.min(window.innerHeight - 12, view.bottom - 12);
7071
+ if (rect.bottom < top || rect.top > bottom) {
7072
+ setOpen(null);
7073
+ return;
7074
+ }
7075
+ const width = Math.min(420, right - left);
7076
+ const above = rect.top - top - 8, below = bottom - rect.bottom - 8;
7077
+ const preferAbove = above >= Math.min(280, window.innerHeight / 3) || above >= below;
7078
+ setPosition({
7079
+ width,
7080
+ left: Math.max(left, Math.min(rect.left, right - width)),
7081
+ maxHeight: Math.min(280, window.innerHeight / 3, preferAbove ? above : below),
7082
+ ...preferAbove ? { bottom: window.innerHeight - rect.top + 8 } : { top: rect.bottom + 8 }
7083
+ });
7084
+ };
7085
+ place();
7086
+ window.addEventListener("scroll", place, true);
7087
+ window.addEventListener("resize", place);
7088
+ return () => {
7089
+ window.removeEventListener("scroll", place, true);
7090
+ window.removeEventListener("resize", place);
7091
+ };
7092
+ }, [expanded]);
7093
+ (0, react.useEffect)(() => {
7094
+ if (!expanded) return;
7095
+ const outside = (event) => {
7096
+ if (!anchor.current?.contains(event.target) && !panel.current?.contains(event.target)) {
7097
+ clearTimer();
7098
+ setOpen(null);
7099
+ }
7100
+ };
7101
+ const escape = (event) => {
7102
+ if (event.key === "Escape") {
7103
+ event.preventDefault();
7104
+ clearTimer();
7105
+ setOpen(null);
7106
+ anchor.current?.focus();
7107
+ }
7108
+ };
7109
+ document.addEventListener("pointerdown", outside);
7110
+ document.addEventListener("keydown", escape);
7111
+ return () => {
7112
+ document.removeEventListener("pointerdown", outside);
7113
+ document.removeEventListener("keydown", escape);
7114
+ };
7115
+ }, [expanded]);
7116
+ (0, react.useLayoutEffect)(() => {
7117
+ if (open?.pinned && document.activeElement === anchor.current) panel.current?.focus();
7118
+ }, [open?.pinned]);
7119
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7120
+ className: annotations_module_css_default.sentChipGroup,
7121
+ children: [interactive ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7122
+ ref: anchor,
7123
+ type: "button",
7124
+ className: annotations_module_css_default.sentChip,
7125
+ "aria-haspopup": "dialog",
7126
+ "aria-expanded": expanded,
7127
+ "aria-controls": id,
7128
+ onPointerEnter: (event) => {
7129
+ if (event.pointerType === "touch") return;
7130
+ clearTimer();
7131
+ if (!open && !document.querySelector("[data-sidenote-sent-preview][data-pinned]")) timer.current = setTimeout(() => {
7132
+ setOpen({ pinned: false });
7133
+ }, 180);
7134
+ },
7135
+ onPointerLeave: leave,
7136
+ onClick: () => {
7137
+ clearTimer();
7138
+ setOpen(open?.pinned ? null : { pinned: true });
7139
+ },
7140
+ children: [
7141
+ icon,
7142
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }),
7143
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronDownOutline14, {
7144
+ size: 12,
7145
+ className: expanded ? annotations_module_css_default.sentChevronOpen : annotations_module_css_default.sentChevron
7146
+ })
7147
+ ]
7148
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7149
+ className: annotations_module_css_default.sentChip,
7150
+ children: [icon, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
7151
+ }), interactive && expanded && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7152
+ ref: panel,
7153
+ id,
7154
+ role: "dialog",
7155
+ tabIndex: -1,
7156
+ "aria-label": label,
7157
+ className: `${annotations_module_css_default.contextPanel} ${annotations_module_css_default.sentPreview}`,
7158
+ style: position,
7159
+ "data-sidenote-sent-preview": true,
7160
+ "data-pinned": open.pinned || void 0,
7161
+ "data-sidenote-no-quote": true,
7162
+ onPointerEnter: clearTimer,
7163
+ onPointerLeave: leave,
7164
+ children
7165
+ }), document.body)]
7166
+ });
7167
+ }
7168
+ function SentContext({ protocol, interactive = true }) {
7169
+ useLocaleTick();
7170
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7171
+ className: annotations_module_css_default.sentChips,
7172
+ "data-dsh-sidenote-bubble": true,
7173
+ "data-sidenote-no-quote": true,
7174
+ children: [protocol.annotations.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextChip, {
7175
+ label: t("sentChipLabel", { n: protocol.annotations.length }),
7176
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconListPenOutline16, { size: 12 }),
7177
+ interactive,
7178
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
7179
+ className: annotations_module_css_default.contextList,
7180
+ children: protocol.annotations.map((annotation) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
7181
+ className: annotations_module_css_default.contextItem,
7182
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7183
+ className: annotations_module_css_default.contextNumber,
7184
+ children: [annotation.id, "."]
7185
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7186
+ className: annotations_module_css_default.contextContent,
7187
+ children: [
7188
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7189
+ className: annotations_module_css_default.contextLabel,
7190
+ children: t("selectedText")
7191
+ }),
7192
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7193
+ className: annotations_module_css_default.contextText,
7194
+ children: annotation.quote
7195
+ }),
7196
+ annotation.note && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7197
+ className: annotations_module_css_default.contextLabel,
7198
+ children: t("userComment")
7199
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7200
+ className: annotations_module_css_default.contextText,
7201
+ children: annotation.note
7202
+ })] })
7203
+ ]
7204
+ })]
7205
+ }, annotation.id))
7206
+ })
7207
+ }), protocol.reflows.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextChip, {
7208
+ label: t("reflowBubbleLabel"),
7209
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconShareOutline16, { size: 12 }),
7210
+ interactive,
7211
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
7212
+ className: annotations_module_css_default.contextList,
7213
+ children: protocol.reflows.map((reflow, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
7214
+ className: annotations_module_css_default.contextItem,
7215
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7216
+ className: annotations_module_css_default.contextNumber,
7217
+ children: [index + 1, "."]
7218
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7219
+ className: annotations_module_css_default.contextContent,
7220
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7221
+ className: annotations_module_css_default.contextSource,
7222
+ children: reflow.source
7223
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7224
+ className: annotations_module_css_default.contextText,
7225
+ children: flattenReflowContent(reflow.content)
7226
+ })]
7227
+ })]
7228
+ }, index))
7229
+ })
7230
+ })]
7231
+ });
7232
+ }
7233
+ function LegacyContext({ Original, props, protocol }) {
7234
+ const root = (0, react.useRef)(null);
7235
+ const [seat] = (0, react.useState)(() => document.createElement("span"));
7236
+ (0, react.useLayoutEffect)(() => root.current ? mountUserBubbleAddon(root.current, seat) : void 0, [seat, props.node]);
7237
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7238
+ ref: root,
7239
+ className: annotations_module_css_default.userContextHost,
7240
+ children: [(0, react.createElement)(Original, props), (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SentContext, { protocol }), seat)]
7241
+ });
7242
+ }
7243
+ /** Keep contextual chips inside the native bubble on both main and side chat.
7244
+ * Only the progress snapshot remains outside its owning user message. */
7245
+ function registerUserContext(ctx) {
7246
+ ctx.inject(["uiConversation"], () => {
7247
+ const ui = ctx.get("uiConversation");
7248
+ const nativeText = ui.userText !== void 0;
7249
+ if (nativeText) ctx.slots.inject("conversation.user.text.node", () => {
7250
+ const offRenderer = ctx.slots.register({
7251
+ name: "conversation.user.text.node",
7252
+ key: CONTEXT_NODE
7253
+ }, (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SentContext, {
7254
+ protocol: props.node.data,
7255
+ interactive: props.interactive
7256
+ }));
7257
+ const offRule = ui.userText.register(sentContextRule);
7258
+ return () => {
7259
+ offRule();
7260
+ offRenderer();
7261
+ };
7262
+ });
7263
+ const slots = ctx.slots;
7264
+ ctx.slots.inject("conversation.chat.node", () => {
7265
+ const patched = /* @__PURE__ */ new Map();
7266
+ const patch = () => {
7267
+ for (const entry of slots.entries("conversation.chat.node")) {
7268
+ if (!["user", "steering"].includes(entry.options.key ?? "") || patched.has(entry)) continue;
7269
+ const Original = entry.component;
7270
+ function ContextUser(props) {
7271
+ const injected = injectedReflowPrefix(props.useChat((state) => state.legacy.nodes), props.node.data.seq);
7272
+ const blocks = props.node.data.content;
7273
+ const at = blocks.findIndex((block) => block.type === "text");
7274
+ const text = blocks[at]?.text ?? "";
7275
+ const snapshot = splitSnapshotPrefix(text);
7276
+ const afterSnapshot = (injected ? injected + "\n\n" : "") + (snapshot?.rest ?? text);
7277
+ const protocol = nativeText ? null : splitProtocolPrefix(afterSnapshot);
7278
+ if (!snapshot && !protocol && !injected) return (0, react.createElement)(Original, props);
7279
+ const displayText = (protocol ? afterSnapshot.slice(protocol.length).trimStart() : afterSnapshot) || (protocol ? "⁠" : "");
7280
+ const content = at < 0 ? [...blocks, {
7281
+ type: "text",
7282
+ text: displayText
7283
+ }] : blocks.map((block, index) => index === at ? {
7284
+ ...block,
7285
+ text: displayText
7286
+ } : block);
7287
+ const projected = {
7288
+ ...props,
7289
+ node: {
7290
+ ...props.node,
7291
+ data: {
7292
+ ...props.node.data,
7293
+ content
7294
+ }
7295
+ }
7296
+ };
7297
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [snapshot && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
7298
+ className: annotations_module_css_default.sentContext,
7299
+ "data-sidenote-progress-snapshot": true,
7300
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("snapshotContext") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: snapshot.snapshot })]
7301
+ }), protocol ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LegacyContext, {
7302
+ Original,
7303
+ props: projected,
7304
+ protocol
7305
+ }) : (0, react.createElement)(Original, projected)] });
7306
+ }
7307
+ entry.component = ContextUser;
7308
+ patched.set(entry, {
7309
+ original: Original,
7310
+ wrapped: ContextUser
7311
+ });
7312
+ }
7313
+ };
7314
+ patch();
7315
+ const off = slots.subscribe("conversation.chat.node", patch);
7316
+ return () => {
7317
+ off();
7318
+ for (const [entry, value] of patched) if (entry.component === value.wrapped) entry.component = value.original;
7319
+ };
7320
+ });
7321
+ });
7322
+ }
7323
+ //#endregion
6791
7324
  //#region src/client/annotate/index.tsx
6792
7325
  /**
6793
7326
  * 划选注释(Delivery_02 重构后):selection listener + numbered badges +
@@ -6816,29 +7349,21 @@ ${entries.map((entry) => {
6816
7349
  store,
6817
7350
  controller
6818
7351
  }));
6819
- const offInterceptor = installSendInterceptor(ctx, store, reflow);
7352
+ registerNativeComposer(ctx, store, reflow);
6820
7353
  registerUserContext(ctx);
6821
7354
  ctx.slots.inject("conversation.input.dock", () => {
6822
- const offAnnotations = ctx.slots.register({
7355
+ const offContext = ctx.slots.register({
6823
7356
  name: "conversation.input.dock",
6824
7357
  id: "dsh-sidenote-annotations",
6825
7358
  order: 10,
6826
7359
  registrant: "dsh-sidenote"
6827
- }, createAnnotationChip(ctx, store, reflow));
6828
- const offReflow = ctx.slots.register({
6829
- name: "conversation.input.dock",
6830
- id: "dsh-sidenote-reflow",
6831
- order: 11,
6832
- registrant: "dsh-sidenote"
6833
- }, createReflowChip(reflow));
7360
+ }, createContextChips(ctx, store, reflow));
6834
7361
  hostDiagnostics(ctx).dockRegistered();
6835
7362
  return () => {
6836
- offAnnotations();
6837
- offReflow();
7363
+ offContext();
6838
7364
  };
6839
7365
  });
6840
7366
  return () => {
6841
- offInterceptor();
6842
7367
  controller.dispose();
6843
7368
  setTimeout(() => {
6844
7369
  root.unmount();