@ahggg/dsh-side-chat 0.7.3 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +52 -20
  2. package/README.zh-CN.md +52 -20
  3. package/lib/client/apply.js +48 -32
  4. package/lib/client/contracts.d.ts +3 -58
  5. package/lib/client/dsh/ReadOnlyConversation.d.ts +10 -0
  6. package/lib/client/dsh/ReadOnlyConversation.js +99 -0
  7. package/lib/client/dsh/SideChatOverlay.d.ts +7 -0
  8. package/lib/client/{rc6/Rc6SideChatOverlay.js → dsh/SideChatOverlay.js} +26 -56
  9. package/lib/client/{rc6 → dsh}/context.d.ts +4 -6
  10. package/lib/client/dsh/conversation-store.d.ts +22 -0
  11. package/lib/client/dsh/conversation-store.js +19 -0
  12. package/lib/client/dsh/primitive-labels.d.ts +2 -0
  13. package/lib/client/dsh/primitive-labels.js +4 -0
  14. package/lib/client/{rc6 → dsh}/remote-adapter.d.ts +2 -2
  15. package/lib/client/dsh/remote-adapter.js +22 -0
  16. package/lib/client/dsh/sessions-adapter.d.ts +56 -0
  17. package/lib/client/dsh/sessions-adapter.js +185 -0
  18. package/lib/client/index.d.ts +2 -2
  19. package/lib/client/panel/SelectionQuote.js +8 -1
  20. package/lib/client/panel/SideChatErrorState.js +1 -1
  21. package/lib/client/panel/SideChatModelSelect.d.ts +2 -1
  22. package/lib/client/panel/SideChatModelSelect.js +43 -24
  23. package/lib/client/panel/SideChatPanel.js +12 -5
  24. package/lib/client/panel/messages.d.ts +1 -1
  25. package/lib/client/panel/messages.js +8 -8
  26. package/lib/client/parent-composer/ParentConversationAnnotations.d.ts +3 -2
  27. package/lib/client/parent-composer/ParentConversationAnnotations.js +50 -30
  28. package/lib/client/parent-composer/add-to-conversation.d.ts +14 -28
  29. package/lib/client/parent-composer/add-to-conversation.js +131 -545
  30. package/lib/client/parent-composer/annotation-persistence.d.ts +8 -8
  31. package/lib/client/parent-composer/annotation-persistence.js +106 -102
  32. package/lib/client/parent-composer/composer-reference.d.ts +17 -55
  33. package/lib/client/parent-composer/composer-reference.js +20 -72
  34. package/lib/client/parent-composer/referenced-conversation.d.ts +5 -7
  35. package/lib/client/parent-composer/referenced-conversation.js +21 -73
  36. package/lib/client/selection/ConversationAnnotationMarkers.js +5 -1
  37. package/lib/client/selection/SelectionActions.js +2 -1
  38. package/lib/client/selection/conversation-dom.d.ts +5 -0
  39. package/lib/client/selection/conversation-dom.js +24 -0
  40. package/lib/client/selection/selection-controller.d.ts +1 -1
  41. package/lib/client/selection/selection-controller.js +7 -3
  42. package/lib/client/selection/selection-normalizer.js +1 -1
  43. package/lib/client/side-chat-controller.d.ts +28 -41
  44. package/lib/client/side-chat-controller.js +247 -337
  45. package/lib/client.js +84 -54
  46. package/lib/host/index.d.ts +1 -1
  47. package/lib/host/index.js +1 -1
  48. package/lib/host/read-only-chat-service.d.ts +30 -0
  49. package/lib/host/read-only-chat-service.js +315 -0
  50. package/lib/index.d.ts +16 -8
  51. package/lib/index.js +34 -13
  52. package/lib/remote.d.ts +23 -14
  53. package/lib/remote.js +2 -5
  54. package/lib/shared/constants.d.ts +0 -13
  55. package/lib/shared/constants.js +0 -13
  56. package/lib/shared/contracts.d.ts +52 -19
  57. package/lib/shared/contracts.js +2 -3
  58. package/lib/shared/error-codes.d.ts +1 -1
  59. package/lib/shared/error-codes.js +2 -2
  60. package/lib/shared/{archived-wire.d.ts → side-chat-wire.d.ts} +109 -20
  61. package/lib/shared/side-chat-wire.js +34 -0
  62. package/lib/typert.d.ts +3 -68
  63. package/lib/typert.js +60 -4
  64. package/package.json +76 -56
  65. package/lib/client/rc6/ArchivedConversation.d.ts +0 -14
  66. package/lib/client/rc6/ArchivedConversation.js +0 -231
  67. package/lib/client/rc6/Rc6SideChatOverlay.d.ts +0 -7
  68. package/lib/client/rc6/SideChatTool.d.ts +0 -22
  69. package/lib/client/rc6/SideChatTool.js +0 -404
  70. package/lib/client/rc6/primitive-labels.d.ts +0 -7
  71. package/lib/client/rc6/primitive-labels.js +0 -60
  72. package/lib/client/rc6/remote-adapter.js +0 -34
  73. package/lib/client/rc6/runtime-compat.d.ts +0 -120
  74. package/lib/client/rc6/runtime-compat.js +0 -187
  75. package/lib/client/rc6/sessions-adapter.d.ts +0 -64
  76. package/lib/client/rc6/sessions-adapter.js +0 -387
  77. package/lib/host/archived-fork-service.d.ts +0 -28
  78. package/lib/host/archived-fork-service.js +0 -309
  79. package/lib/shared/archived-wire.js +0 -47
  80. /package/lib/client/{rc6 → dsh}/context.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useId, useMemo, useRef, useState, useSyncExternalStore, } from 'react';
3
- import { IconCheckOutline16, IconChevronDownOutline14, IconChevronRightOutline14, IconWarningOutline16, Toast, } from '@deepseek-ai/dsh-client-ui-primitives';
3
+ import { IconCheckOutlineRegular as IconCheckOutline16, IconChevronDownOutlineRegular as IconChevronDownOutline14, IconChevronRightOutlineRegular as IconChevronRightOutline14, IconWarningOutlineRegular as IconWarningOutline16, Toast, } from '@deepseek-ai/dsh-client-ui-primitives';
4
4
  const MODEL_MESSAGES = {
5
5
  en: {
6
6
  selectModel: 'Select model',
@@ -12,6 +12,7 @@ const MODEL_MESSAGES = {
12
12
  reload: 'Reload',
13
13
  emptyModels: 'No models available.',
14
14
  emptyEfforts: 'This model provides no reasoning effort levels.',
15
+ nextReply: 'Changes apply to the next reply and future Side Chats.',
15
16
  aria: (model, effort) => effort === undefined
16
17
  ? `Select model, current ${model}`
17
18
  : `Select model, current ${model}, reasoning effort ${effort}`,
@@ -28,6 +29,7 @@ const MODEL_MESSAGES = {
28
29
  reload: '重新加载',
29
30
  emptyModels: '没有可用的模型。',
30
31
  emptyEfforts: '当前模型未提供推理等级。',
32
+ nextReply: '修改将用于下一次回复及之后新开的侧边对话。',
31
33
  aria: (model, effort) => effort === undefined
32
34
  ? `选择模型,当前 ${model}`
33
35
  : `选择模型,当前 ${model},推理等级 ${effort}`,
@@ -61,7 +63,7 @@ function repairSelection(selection, choices, fallback) {
61
63
  };
62
64
  }
63
65
  /** Side Chat projection of DSH Web's native composer model selector. */
64
- export function SideChatModelSelect({ directory, selection, locked, validateInitialSelection = true, locale = 'en', onInitialize, onSelect, }) {
66
+ export function SideChatModelSelect({ directory, selection, locked, running = false, validateInitialSelection = true, locale = 'en', onInitialize, onSelect, }) {
65
67
  const messages = MODEL_MESSAGES[locale];
66
68
  const state = useSyncExternalStore(listener => directory.store.subscribe(listener), () => directory.store.getSnapshot(), () => directory.store.getSnapshot());
67
69
  const [open, setOpen] = useState(false);
@@ -74,6 +76,7 @@ export function SideChatModelSelect({ directory, selection, locked, validateInit
74
76
  const initialized = useRef(false);
75
77
  const operation = useRef(0);
76
78
  const toastSequence = useRef(0);
79
+ const selectionPending = useRef(false);
77
80
  const id = useId();
78
81
  const choices = useMemo(() => state.groups.flatMap(group => group.models.map(model => ({ group, model }))), [state.groups]);
79
82
  const current = selection ?? state.current ?? undefined;
@@ -98,14 +101,20 @@ export function SideChatModelSelect({ directory, selection, locked, validateInit
98
101
  ...(effort.description === undefined ? {} : { description: effort.description }),
99
102
  })),
100
103
  ], [messages.providerDefault, reasoning]);
101
- const modelLabel = currentChoice?.model.name ?? messages.selectModel;
104
+ const modelLabel = currentChoice?.model.name ?? current?.model ?? messages.selectModel;
102
105
  const triggerLabel = effortLabel === undefined ? modelLabel : `${modelLabel} · ${effortLabel}`;
103
106
  const reload = () => {
104
107
  void directory.load().catch(() => undefined);
105
108
  };
106
- useEffect(() => { reload(); }, [directory]);
107
109
  useEffect(() => {
108
- if (initialized.current)
110
+ initialized.current = false;
111
+ reload();
112
+ return () => { ++operation.current; };
113
+ }, [directory]);
114
+ useEffect(() => { if (locked)
115
+ setOpen(false); }, [locked]);
116
+ useEffect(() => {
117
+ if (initialized.current || locked)
109
118
  return;
110
119
  if (selection !== undefined && !validateInitialSelection) {
111
120
  initialized.current = true;
@@ -125,13 +134,13 @@ export function SideChatModelSelect({ directory, selection, locked, validateInit
125
134
  onInitialize(directoryCurrent, { remember: false });
126
135
  return;
127
136
  }
128
- if (state.status !== 'ready')
137
+ if (state.status !== 'ready' || state.error !== null || state.failures.length > 0)
129
138
  return;
130
139
  initialized.current = true;
131
140
  const repaired = repairSelection(selection, choices, directoryCurrent);
132
141
  if (!sameSelection(selection, repaired))
133
142
  onInitialize(repaired, { remember: true });
134
- }, [choices, onInitialize, selection, state.current, state.status, validateInitialSelection]);
143
+ }, [choices, locked, onInitialize, selection, state.current, state.status, state.error, state.failures, validateInitialSelection]);
135
144
  useEffect(() => {
136
145
  if (!open)
137
146
  return;
@@ -163,6 +172,7 @@ export function SideChatModelSelect({ directory, selection, locked, validateInit
163
172
  const onKeyDown = (event) => {
164
173
  if (event.key === 'Escape' && open) {
165
174
  event.preventDefault();
175
+ event.stopPropagation();
166
176
  if (pane !== 'root')
167
177
  setPane('root');
168
178
  else
@@ -180,21 +190,32 @@ export function SideChatModelSelect({ directory, selection, locked, validateInit
180
190
  close();
181
191
  };
182
192
  const submitSelection = async (next) => {
193
+ if (locked || selectionPending.current)
194
+ return;
183
195
  const generation = ++operation.current;
196
+ selectionPending.current = true;
184
197
  setSelecting(true);
185
- const result = await onSelect(next);
186
- if (generation !== operation.current)
187
- return;
188
- setSelecting(false);
189
- if (!result.ok) {
190
- toastSequence.current += 1;
198
+ try {
199
+ const result = await onSelect(next);
200
+ if (generation !== operation.current)
201
+ return;
202
+ if (!result.ok)
203
+ throw new Error(result.error.message);
204
+ close(true);
205
+ }
206
+ catch (error) {
207
+ if (generation !== operation.current)
208
+ return;
191
209
  setToast({
192
- seq: toastSequence.current,
193
- text: messages.operationFailed(result.error.message),
210
+ seq: ++toastSequence.current,
211
+ text: messages.operationFailed(error instanceof Error ? error.message : 'Could not select the model.'),
194
212
  });
195
- return;
196
213
  }
197
- close(true);
214
+ finally {
215
+ selectionPending.current = false;
216
+ if (generation === operation.current)
217
+ setSelecting(false);
218
+ }
198
219
  };
199
220
  const chooseModel = (group, model) => {
200
221
  if (sameModel(current, group.id, model.id)) {
@@ -212,10 +233,8 @@ export function SideChatModelSelect({ directory, selection, locked, validateInit
212
233
  const chooseEffort = (effort) => {
213
234
  if (current === undefined)
214
235
  return;
215
- if (effectiveEffort === effort) {
216
- close(true);
217
- return;
218
- }
236
+ // Choosing the displayed default is still an explicit preference: persist
237
+ // it for future Side Chats instead of merely dismissing the menu.
219
238
  void submitSelection({
220
239
  provider: current.provider,
221
240
  model: current.model,
@@ -231,16 +250,16 @@ export function SideChatModelSelect({ directory, selection, locked, validateInit
231
250
  return (_jsxs("div", { ref: rootRef, className: "dsh-side-chat-model-root", "data-side-chat-model-select": "", onKeyDown: onKeyDown, onBlur: onBlur, children: [_jsxs("button", { ref: triggerRef, type: "button", className: "dsh-side-chat-model-trigger", "aria-label": messages.aria(modelLabel, effortLabel), "aria-haspopup": "menu", "aria-expanded": open, "aria-controls": open ? `${id}-menu` : undefined, title: triggerLabel, disabled: locked, onClick: () => { if (open)
232
251
  close();
233
252
  else
234
- show(); }, children: [_jsx("span", { className: "dsh-side-chat-model-trigger-label", children: modelLabel }), effortLabel !== undefined && (_jsx("span", { className: "dsh-side-chat-model-trigger-effort", children: effortLabel })), _jsx(IconChevronDownOutline14, { className: `dsh-side-chat-model-chevron${open ? ' dsh-side-chat-model-chevron-open' : ''}` })] }), open && (_jsxs("div", { id: `${id}-menu`, className: "dsh-side-chat-model-menu", role: "menu", "aria-label": messages.menu, "aria-busy": state.status === 'loading' || selecting, children: [pane === 'root' && (_jsxs(_Fragment, { children: [_jsxs("button", { ref: itemRef(), type: "button", role: "menuitem", className: "dsh-side-chat-model-cell", onClick: () => { setPane('model'); }, children: [_jsx("span", { className: "dsh-side-chat-model-cell-label", children: messages.model }), _jsx("span", { className: "dsh-side-chat-model-cell-value", children: modelLabel }), _jsx(IconChevronRightOutline14, { className: "dsh-side-chat-model-cell-chevron" })] }), reasoning !== undefined && (_jsxs("button", { ref: itemRef(), type: "button", role: "menuitem", className: "dsh-side-chat-model-cell", onClick: () => { setPane('effort'); }, children: [_jsx("span", { className: "dsh-side-chat-model-cell-label", children: messages.effort }), _jsx("span", { className: "dsh-side-chat-model-cell-value", children: effortLabel }), _jsx(IconChevronRightOutline14, { className: "dsh-side-chat-model-cell-chevron" })] }))] })), pane === 'model' && (_jsxs(_Fragment, { children: [state.status === 'loading' && (_jsx("div", { className: "dsh-side-chat-model-status", children: messages.loading })), state.error !== null && (_jsxs("div", { className: "dsh-side-chat-model-error", role: "alert", children: [_jsx("span", { children: messages.operationFailed(state.error) }), _jsx("button", { type: "button", className: "dsh-side-chat-model-retry", onClick: reload, children: messages.reload })] })), state.failures.map(failure => (_jsxs("div", { className: "dsh-side-chat-model-warning", children: [_jsx("span", { children: messages.groupFailed(failure.name, failure.message) }), _jsx("button", { type: "button", className: "dsh-side-chat-model-retry", onClick: reload, children: messages.reload })] }, failure.id))), _jsx("div", { className: "dsh-side-chat-model-groups scrollable", children: state.groups.map(group => {
253
+ show(); }, children: [_jsx("span", { className: "dsh-side-chat-model-trigger-label", children: modelLabel }), effortLabel !== undefined && (_jsx("span", { className: "dsh-side-chat-model-trigger-effort", children: effortLabel })), _jsx(IconChevronDownOutline14, { className: `dsh-side-chat-model-chevron${open ? ' dsh-side-chat-model-chevron-open' : ''}` })] }), open && (_jsxs("div", { id: `${id}-menu`, className: "dsh-side-chat-model-menu", role: "menu", "aria-label": messages.menu, "aria-busy": state.status === 'loading' || selecting, children: [running && _jsx("div", { className: "dsh-side-chat-model-status", children: messages.nextReply }), pane === 'root' && (_jsxs(_Fragment, { children: [_jsxs("button", { ref: itemRef(), type: "button", role: "menuitem", className: "dsh-side-chat-model-cell", onClick: () => { setPane('model'); }, children: [_jsx("span", { className: "dsh-side-chat-model-cell-label", children: messages.model }), _jsx("span", { className: "dsh-side-chat-model-cell-value", children: modelLabel }), _jsx(IconChevronRightOutline14, { className: "dsh-side-chat-model-cell-chevron" })] }), reasoning !== undefined && (_jsxs("button", { ref: itemRef(), type: "button", role: "menuitem", className: "dsh-side-chat-model-cell", onClick: () => { setPane('effort'); }, children: [_jsx("span", { className: "dsh-side-chat-model-cell-label", children: messages.effort }), _jsx("span", { className: "dsh-side-chat-model-cell-value", children: effortLabel }), _jsx(IconChevronRightOutline14, { className: "dsh-side-chat-model-cell-chevron" })] }))] })), pane === 'model' && (_jsxs(_Fragment, { children: [state.status === 'loading' && (_jsx("div", { className: "dsh-side-chat-model-status", children: messages.loading })), state.error !== null && (_jsxs("div", { className: "dsh-side-chat-model-error", role: "alert", children: [_jsx("span", { children: messages.operationFailed(state.error) }), _jsx("button", { type: "button", className: "dsh-side-chat-model-retry", onClick: reload, children: messages.reload })] })), state.failures.map(failure => (_jsxs("div", { className: "dsh-side-chat-model-warning", children: [_jsx("span", { children: messages.groupFailed(failure.name, failure.message) }), _jsx("button", { type: "button", className: "dsh-side-chat-model-retry", onClick: reload, children: messages.reload })] }, failure.id))), _jsx("div", { className: "dsh-side-chat-model-groups scrollable", children: state.groups.map(group => {
235
254
  const headingId = `${id}-${group.id}`;
236
255
  return (_jsxs("section", { role: "group", "aria-labelledby": headingId, className: "dsh-side-chat-model-group", children: [_jsx("div", { id: headingId, className: "dsh-side-chat-model-group-title", children: group.name }), group.models.map(model => {
237
256
  const selected = sameModel(current, group.id, model.id);
238
- return (_jsxs("button", { ref: itemRef(), type: "button", role: "menuitemradio", "aria-checked": selected, className: `dsh-side-chat-model-option${selected ? ' dsh-side-chat-model-selected' : ''}`, title: model.name, disabled: selecting, onClick: () => { chooseModel(group, model); }, children: [_jsxs("span", { className: "dsh-side-chat-model-option-copy", children: [_jsx("span", { className: "dsh-side-chat-model-name", children: model.name }), model.description !== undefined && (_jsx("span", { className: "dsh-side-chat-model-description", children: model.description }))] }), _jsx("span", { className: "dsh-side-chat-model-check", children: selected ? _jsx(IconCheckOutline16, {}) : null })] }, model.id));
257
+ return (_jsxs("button", { ref: itemRef(), type: "button", role: "menuitemradio", "aria-checked": selected, className: `dsh-side-chat-model-option${selected ? ' dsh-side-chat-model-selected' : ''}`, title: model.name, disabled: locked || selecting, onClick: () => { chooseModel(group, model); }, children: [_jsxs("span", { className: "dsh-side-chat-model-option-copy", children: [_jsx("span", { className: "dsh-side-chat-model-name", children: model.name }), model.description !== undefined && (_jsx("span", { className: "dsh-side-chat-model-description", children: model.description }))] }), _jsx("span", { className: "dsh-side-chat-model-check", children: selected ? _jsx(IconCheckOutline16, {}) : null })] }, model.id));
239
258
  })] }, group.id));
240
259
  }) }), state.status === 'ready' && choices.length === 0 && (_jsx("div", { className: "dsh-side-chat-model-empty", children: messages.emptyModels }))] })), pane === 'effort' && (effortChoices.length === 0
241
260
  ? _jsx("div", { className: "dsh-side-chat-model-empty", children: messages.emptyEfforts })
242
261
  : effortChoices.map(level => {
243
262
  const selected = effectiveEffort === level.effort;
244
- return (_jsxs("button", { ref: itemRef(), type: "button", role: "menuitemradio", "aria-checked": selected, className: `dsh-side-chat-model-option${selected ? ' dsh-side-chat-model-selected' : ''}`, disabled: selecting, onClick: () => { chooseEffort(level.effort); }, children: [_jsxs("span", { className: "dsh-side-chat-model-option-copy", children: [_jsx("span", { className: "dsh-side-chat-model-name", children: level.label }), level.description !== undefined && (_jsx("span", { className: "dsh-side-chat-model-description", children: level.description }))] }), _jsx("span", { className: "dsh-side-chat-model-check", children: selected ? _jsx(IconCheckOutline16, {}) : null })] }, level.key));
263
+ return (_jsxs("button", { ref: itemRef(), type: "button", role: "menuitemradio", "aria-checked": selected, className: `dsh-side-chat-model-option${selected ? ' dsh-side-chat-model-selected' : ''}`, disabled: locked || selecting, onClick: () => { chooseEffort(level.effort); }, children: [_jsxs("span", { className: "dsh-side-chat-model-option-copy", children: [_jsx("span", { className: "dsh-side-chat-model-name", children: level.label }), level.description !== undefined && (_jsx("span", { className: "dsh-side-chat-model-description", children: level.description }))] }), _jsx("span", { className: "dsh-side-chat-model-check", children: selected ? _jsx(IconCheckOutline16, {}) : null })] }, level.key));
245
264
  }))] })), toast !== null && (_jsx(Toast, { text: toast.text, icon: _jsx(IconWarningOutline16, {}), anchor: rootRef.current?.closest('[data-composer-card]') ?? null, onDone: () => { setToast(null); } }, toast.seq))] }));
246
265
  }
@@ -13,7 +13,7 @@ export function SideChatPanel({ state, locale = 'en', embeddedConversation, mode
13
13
  const draftRef = useAutoGrowingTextarea(state.draft);
14
14
  const submit = async (event) => {
15
15
  event.preventDefault();
16
- if (submitting)
16
+ if (submitting || ['creating', 'running', 'closing'].includes(state.phase))
17
17
  return;
18
18
  setSubmitting(true);
19
19
  try {
@@ -23,12 +23,19 @@ export function SideChatPanel({ state, locale = 'en', embeddedConversation, mode
23
23
  setSubmitting(false);
24
24
  }
25
25
  };
26
- return (_jsxs("aside", { className: "dsh-side-chat-panel", "data-side-chat-panel": "", "aria-label": messages.title, "aria-busy": ['creating', 'opening', 'closing'].includes(state.phase) || undefined, children: [_jsx(SideChatHeader, { phase: state.phase, messages: messages, ...onAddToConversation === undefined ? {} : { onAddToConversation }, ...addToConversationDisabled === undefined ? {} : { addToConversationDisabled }, onFocusParent: onFocusParent, onClose: () => { void onClose(); } }), state.error !== undefined && (_jsx(SideChatErrorState, { error: state.error, messages: messages, onRetry: () => { void onRetry(); } })), state.childSessionId !== undefined && embeddedConversation !== undefined
26
+ return (_jsxs("aside", { className: "dsh-side-chat-panel", "data-side-chat-panel": "", "aria-label": messages.title, "aria-busy": ['creating', 'closing'].includes(state.phase) || undefined, onKeyDown: (event) => {
27
+ if (event.key === 'Escape' && !event.defaultPrevented && !event.nativeEvent.isComposing) {
28
+ event.preventDefault();
29
+ event.stopPropagation();
30
+ void onClose();
31
+ }
32
+ }, children: [_jsx(SideChatHeader, { phase: state.phase, messages: messages, ...onAddToConversation === undefined ? {} : { onAddToConversation }, ...addToConversationDisabled === undefined ? {} : { addToConversationDisabled }, onFocusParent: onFocusParent, onClose: () => { void onClose(); } }), state.error !== undefined && (_jsx(SideChatErrorState, { error: state.error, messages: messages, onRetry: () => { void onRetry(); } })), state.messages.length > 0 && embeddedConversation !== undefined
27
33
  ? _jsx(SideChatBody, { children: embeddedConversation })
28
- : (_jsxs("form", { className: "dsh-side-chat-draft", "data-composer-card": "", onSubmit: (event) => { void submit(event); }, children: [state.selection !== undefined && (_jsx(SelectionQuote, { selections: [state.selection], messages: messages, ...onRemoveSelection === undefined ? {} : { onRemove: onRemoveSelection } })), _jsx("label", { htmlFor: "dsh-side-chat-draft-input", children: messages.placeholder }), _jsx("textarea", { id: "dsh-side-chat-draft-input", ref: draftRef, autoFocus: true, rows: 1, value: state.draft, disabled: ['creating', 'opening', 'closing'].includes(state.phase), placeholder: messages.placeholder, onChange: (event) => { onDraftChange(event.target.value); }, onKeyDown: (event) => {
29
- if (event.key === 'Enter' && !event.shiftKey) {
34
+ : (_jsxs("form", { className: "dsh-side-chat-draft", "data-composer-card": "", onSubmit: (event) => { void submit(event); }, children: [state.selection !== undefined && (_jsx(SelectionQuote, { selections: [state.selection], messages: messages, ...onRemoveSelection === undefined ? {} : { onRemove: onRemoveSelection } })), _jsx("label", { htmlFor: "dsh-side-chat-draft-input", children: messages.placeholder }), _jsx("textarea", { id: "dsh-side-chat-draft-input", ref: draftRef, autoFocus: true, rows: 1, value: state.draft, disabled: ['creating', 'running', 'closing'].includes(state.phase), placeholder: messages.placeholder, onChange: (event) => { onDraftChange(event.target.value); }, onKeyDown: (event) => {
35
+ if (event.key === 'Enter' && !event.shiftKey && !event.nativeEvent.isComposing
36
+ && event.keyCode !== 229) {
30
37
  event.preventDefault();
31
38
  event.currentTarget.form?.requestSubmit();
32
39
  }
33
- } }), _jsxs("div", { className: "dsh-side-chat-draft-actions", children: [modelControl, _jsx("button", { type: "submit", className: "dsh-side-chat-send-button", "aria-label": messages.send, disabled: submitting || state.draft.trim().length === 0, children: _jsx(SendIcon, {}) })] })] })), _jsxs("footer", { className: "dsh-side-chat-footer", children: [_jsx("span", { children: messages.temporary }), _jsx("span", { children: messages.referenceOnly }), _jsx("span", { children: messages.cannotReopen }), _jsx("span", { children: messages.sharedWorkspace })] }), _jsx("div", { className: "dsh-side-chat-announcer", "aria-live": "polite", children: state.phase === 'running' ? 'Side Chat running' : `Side Chat ${state.phase}` })] }));
40
+ } }), _jsxs("div", { className: "dsh-side-chat-draft-actions", children: [modelControl, _jsx("button", { type: "submit", className: "dsh-side-chat-send-button", "aria-label": messages.send, disabled: submitting || ['creating', 'running', 'closing'].includes(state.phase) || state.draft.trim().length === 0, children: _jsx(SendIcon, {}) })] })] })), _jsxs("footer", { className: "dsh-side-chat-footer", children: [_jsx("span", { children: messages.temporary }), _jsx("span", { children: messages.referenceOnly }), _jsx("span", { children: messages.cannotReopen }), _jsx("span", { children: messages.readOnly })] }), _jsx("div", { className: "dsh-side-chat-announcer", "aria-live": "polite", children: state.phase === 'running' ? 'Side Chat running' : `Side Chat ${state.phase}` })] }));
34
41
  }
@@ -14,7 +14,7 @@ export interface SideChatMessages {
14
14
  readonly temporary: string;
15
15
  readonly referenceOnly: string;
16
16
  readonly cannotReopen: string;
17
- readonly sharedWorkspace: string;
17
+ readonly readOnly: string;
18
18
  readonly retry: string;
19
19
  readonly genericError: string;
20
20
  readonly closeError: string;
@@ -12,10 +12,10 @@ export const SIDE_CHAT_MESSAGES = Object.freeze({
12
12
  removeSelection: 'Remove annotation',
13
13
  expand: 'Expand',
14
14
  collapse: 'Collapse',
15
- temporary: 'Archived when closed; history remains on disk',
16
- referenceOnly: 'Inherits the complete parent conversation prefix',
17
- cannotReopen: 'No reopen action',
18
- sharedWorkspace: 'Shares the parent workspace',
15
+ temporary: 'Temporary; discarded when closed or reloaded',
16
+ referenceOnly: 'Read-only parent context; no Session copy',
17
+ cannotReopen: 'Add to conversation to keep this discussion',
18
+ readOnly: 'No tools or file changes',
19
19
  retry: 'Retry',
20
20
  genericError: 'Side Chat error',
21
21
  closeError: 'Could not close the Side Chat',
@@ -33,10 +33,10 @@ export const SIDE_CHAT_MESSAGES = Object.freeze({
33
33
  removeSelection: '移除引用',
34
34
  expand: '展开',
35
35
  collapse: '收起',
36
- temporary: '关闭时归档;历史仍保存在磁盘上',
37
- referenceOnly: '完整继承父会话对话前缀',
38
- cannotReopen: '不提供重新打开操作',
39
- sharedWorkspace: '与父会话共享工作区',
36
+ temporary: '临时对话;关闭或刷新后丢弃',
37
+ referenceOnly: '只读父会话上下文;不复制 Session',
38
+ cannotReopen: '添加到主对话以保留讨论内容',
39
+ readOnly: '不使用工具,不修改文件',
40
40
  retry: '重试',
41
41
  genericError: '侧边对话错误',
42
42
  closeError: '无法关闭侧边对话',
@@ -1,5 +1,6 @@
1
1
  import { type ElementType, type ReactNode } from 'react';
2
- import type { Rc6ClientContext } from '../rc6/context.js';
2
+ import type { DshClientContext } from '../dsh/context.js';
3
+ import { SessionId } from '../../shared/contracts.js';
3
4
  import { SIDE_CHAT_MESSAGES } from '../panel/messages.js';
4
5
  import { type ParentComposerInputSnapshot } from './add-to-conversation.js';
5
6
  type Locale = keyof typeof SIDE_CHAT_MESSAGES;
@@ -24,5 +25,5 @@ export declare function ParentComposerAnnotations({ input, onRemove, locale, }:
24
25
  /** Wrap DSH's own user renderer only when this plugin's durable prefix exists. */
25
26
  export declare function annotatedUserMessageRenderer(Original: UserNodeRenderer): (props: UserNodeProps) => ReactNode;
26
27
  /** Mount the composer capsule and a thin wrapper around DSH's user renderers. */
27
- export declare function mountParentConversationAnnotations(ctx: Rc6ClientContext, removeAnnotations: () => void): () => void;
28
+ export declare function mountParentConversationAnnotations(ctx: DshClientContext, removeAnnotations: (sessionId: SessionId) => void): () => void;
28
29
  export {};
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createElement, useLayoutEffect, useRef } from 'react';
3
+ import { SessionId } from '../../shared/contracts.js';
3
4
  import { SIDE_CHAT_MESSAGES } from '../panel/messages.js';
4
5
  import { SelectionQuote } from '../panel/SelectionQuote.js';
5
- import { conversationAnnotations, parseAnnotatedConversationPrompt, } from './add-to-conversation.js';
6
+ import { annotationMessageText, conversationAnnotations, parseAnnotatedConversationPrompt, } from './add-to-conversation.js';
6
7
  import { parseReferencedConversationPrompt, } from './referenced-conversation.js';
7
8
  function currentLocale() {
8
9
  return navigator.language.toLowerCase().startsWith('zh') ? 'zh-CN' : 'en';
@@ -16,24 +17,49 @@ export function ParentComposerAnnotations({ input, onRemove, locale = currentLoc
16
17
  const dock = dockRef.current;
17
18
  const seat = dock?.closest('[data-composer-seat]');
18
19
  const chip = dock?.querySelector('.dsh-side-chat-quote-chip');
19
- if (seat === undefined || chip === undefined || seat === null || chip === null)
20
+ if (dock === null || seat === undefined || chip === undefined || seat === null || chip === null)
20
21
  return;
21
22
  // The durable host reference stays in Lexical's document flow. Match its
22
23
  // occupied width to this visible overlay so the caret starts at the edge
23
24
  // users see instead of after the longer internal serialization label.
24
- const syncWidth = () => {
25
+ const syncPosition = () => {
25
26
  const width = chip.getBoundingClientRect().width;
26
27
  if (width > 0)
27
28
  seat.style.setProperty(ANNOTATION_CHIP_WIDTH_PROPERTY, `${String(width)}px`);
29
+ const reference = seat.querySelector('[data-composer-chip="dsh-side-chat-selection"]');
30
+ if (reference === null)
31
+ return;
32
+ const referenceRect = reference.getBoundingClientRect();
33
+ const dockRect = dock.getBoundingClientRect();
34
+ if (Number.isFinite(referenceRect.left) && Number.isFinite(dockRect.left)) {
35
+ dock.style.setProperty('--annotation-left', `${referenceRect.left - dockRect.left}px`);
36
+ dock.style.setProperty('--annotation-top', `${referenceRect.top - dockRect.top}px`);
37
+ }
28
38
  };
29
- syncWidth();
30
- const observer = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(syncWidth);
39
+ let frame;
40
+ const schedule = () => {
41
+ if (frame !== undefined)
42
+ return;
43
+ frame = window.requestAnimationFrame(() => { frame = undefined; syncPosition(); });
44
+ };
45
+ syncPosition();
46
+ const observer = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(schedule);
31
47
  observer?.observe(chip);
48
+ observer?.observe(seat);
49
+ const mutations = new MutationObserver(schedule);
50
+ mutations.observe(seat, { childList: true, characterData: true, subtree: true });
51
+ seat.addEventListener('scroll', schedule, true);
52
+ window.addEventListener('resize', schedule);
32
53
  return () => {
54
+ if (frame !== undefined)
55
+ window.cancelAnimationFrame(frame);
33
56
  observer?.disconnect();
57
+ mutations.disconnect();
58
+ seat.removeEventListener('scroll', schedule, true);
59
+ window.removeEventListener('resize', schedule);
34
60
  seat.style.removeProperty(ANNOTATION_CHIP_WIDTH_PROPERTY);
35
61
  };
36
- }, [annotations.length, locale]);
62
+ }, [input, locale]);
37
63
  if (annotations.length === 0)
38
64
  return null;
39
65
  return (_jsx("div", { ref: dockRef, className: "dsh-side-chat-parent-annotation-dock", children: _jsx(SelectionQuote, { selections: annotations, messages: SIDE_CHAT_MESSAGES[locale], onRemove: onRemove }) }));
@@ -46,10 +72,6 @@ function contentText(content) {
46
72
  return value.type === 'text' && typeof value.text === 'string' ? value.text : '';
47
73
  }).join('');
48
74
  }
49
- function visibleMessage(message) {
50
- const wrapped = /^<user_question>\n?([\s\S]*?)\n?<\/user_question>$/u.exec(message);
51
- return wrapped?.[1]?.trim() ?? message;
52
- }
53
75
  function replaceTextContent(content, message) {
54
76
  let replaced = false;
55
77
  const next = content.flatMap((block) => {
@@ -68,29 +90,25 @@ function replaceTextContent(content, message) {
68
90
  function parsePluginPrompt(text) {
69
91
  let message = text;
70
92
  let annotations;
71
- let reference;
72
- for (let index = 0; index < 2; index += 1) {
93
+ const references = [];
94
+ while (message.length > 0) {
73
95
  const annotated = annotations === undefined ? parseAnnotatedConversationPrompt(message) : undefined;
74
96
  if (annotated !== undefined) {
75
97
  annotations = annotated.annotations;
76
98
  message = annotated.message;
77
99
  continue;
78
100
  }
79
- const referenced = reference === undefined ? parseReferencedConversationPrompt(message) : undefined;
101
+ const referenced = parseReferencedConversationPrompt(message);
80
102
  if (referenced !== undefined) {
81
- reference = referenced.reference;
103
+ references.push(referenced.reference);
82
104
  message = referenced.message;
83
105
  continue;
84
106
  }
85
107
  break;
86
108
  }
87
- if (annotations === undefined && reference === undefined)
109
+ if (annotations === undefined && references.length === 0)
88
110
  return;
89
- return {
90
- ...(annotations === undefined ? {} : { annotations }),
91
- ...(reference === undefined ? {} : { reference }),
92
- message,
93
- };
111
+ return { ...(annotations === undefined ? {} : { annotations }), references, message };
94
112
  }
95
113
  /** Wrap DSH's own user renderer only when this plugin's durable prefix exists. */
96
114
  export function annotatedUserMessageRenderer(Original) {
@@ -98,19 +116,16 @@ export function annotatedUserMessageRenderer(Original) {
98
116
  const parsed = parsePluginPrompt(contentText(props.node.data.content));
99
117
  if (parsed === undefined)
100
118
  return createElement(Original, props);
101
- const body = visibleMessage(parsed.message);
102
- const visible = parsed.reference === undefined
103
- ? body
104
- : [`@${parsed.reference.title}`, body].filter(part => part.length > 0).join('\n\n');
119
+ const body = parsed.annotations === undefined ? parsed.message : annotationMessageText(parsed.message);
120
+ const visible = [...parsed.references.map(reference => `@${reference.title}`), body]
121
+ .filter(part => part.length > 0).join('\n\n');
105
122
  const existingLabels = props.node.data.referenceLabels ?? [];
106
123
  const node = {
107
124
  ...props.node,
108
125
  data: {
109
126
  ...props.node.data,
110
127
  content: replaceTextContent(props.node.data.content, visible),
111
- ...(parsed.reference === undefined
112
- ? {}
113
- : { referenceLabels: [...new Set([parsed.reference.title, ...existingLabels])] }),
128
+ referenceLabels: [...new Set([...parsed.references.map(reference => reference.title), ...existingLabels])],
114
129
  },
115
130
  };
116
131
  const original = createElement(Original, { ...props, node });
@@ -128,8 +143,10 @@ export function mountParentConversationAnnotations(ctx, removeAnnotations) {
128
143
  // This zero-height projection must follow Todo (0), Goal (10), and Queue
129
144
  // (20), so its absolute child remains anchored to the input card below.
130
145
  order: 30,
131
- }, ({ input }) => (_jsx(ParentComposerAnnotations, { input: input, onRemove: removeAnnotations }))));
146
+ }, ({ input, session }) => (_jsx(ParentComposerAnnotations, { input: input, onRemove: () => removeAnnotations(SessionId(session.sessionId)) }))));
132
147
  const shadow = (key) => slots.inject('conversation.chat.node', () => {
148
+ let cachedOriginal;
149
+ let CachedRenderer;
133
150
  const ShadowedUserMessage = (props) => {
134
151
  // Built-in keyed renderers can register after this declaration callback.
135
152
  // Resolve the next-priority renderer at render time so load order does
@@ -141,8 +158,11 @@ export function mountParentConversationAnnotations(ctx, removeAnnotations) {
141
158
  ?.component;
142
159
  if (original === undefined)
143
160
  return null;
144
- const Renderer = annotatedUserMessageRenderer(original);
145
- return createElement(Renderer, props);
161
+ if (original !== cachedOriginal || CachedRenderer === undefined) {
162
+ cachedOriginal = original;
163
+ CachedRenderer = annotatedUserMessageRenderer(cachedOriginal);
164
+ }
165
+ return createElement(CachedRenderer, props);
146
166
  };
147
167
  return slots.register({
148
168
  name: 'conversation.chat.node',
@@ -1,50 +1,36 @@
1
1
  import type { ConversationSelection, SideChatPromptPart } from '../../shared/contracts.js';
2
2
  import type { ParentComposerInput, ParentComposerInputSnapshot, SelectionReferenceSource } from './composer-reference.js';
3
3
  export type { ParentComposerInput, ParentComposerInputSnapshot, ParentComposerOccurrence, ParentConversationService, SelectionReferenceSource, } from './composer-reference.js';
4
+ export declare const SELECTION_REFERENCE_SOURCE = "dsh-side-chat-selection";
4
5
  export declare const SELECTION_REFERENCE_LABEL = "__dsh_side_chat_annotations__";
5
6
  export interface ConversationAnnotation {
6
7
  readonly text: string;
7
8
  readonly comment?: string;
8
9
  }
9
- export interface ConversationSelectionAnnotation extends ConversationAnnotation {
10
- /** Zero-based position in the aggregated composer annotation list. */
11
- readonly annotationIndex: number;
10
+ interface StoredConversationAnnotation extends ConversationAnnotation {
12
11
  readonly selection: ConversationSelection;
13
12
  }
13
+ export interface ConversationSelectionAnnotation extends StoredConversationAnnotation {
14
+ readonly annotationIndex: number;
15
+ }
16
+ export declare function unescapeXmlText(value: string): string;
14
17
  export declare function decodeSelectionReference(ref: string): readonly ConversationAnnotation[];
15
- /** Read all plugin annotations represented by the current DSH input occurrence. */
16
18
  export declare function conversationAnnotations(snapshot: ParentComposerInputSnapshot): readonly ConversationAnnotation[];
17
- /** Read annotations that retain an exact source-selection anchor. */
18
19
  export declare function conversationSelectionAnnotations(snapshot: ParentComposerInputSnapshot): readonly ConversationSelectionAnnotation[];
19
- /** Remove every unsent plugin annotation while preserving the user's draft. */
20
- export declare function removeConversationAnnotations(input: ParentComposerInput): boolean;
21
- /** Reference codec used by DSH's native composer chip and submit pipeline. */
22
- export declare const selectionReferenceSource: SelectionReferenceSource;
23
- /** Add one passage to the parent composer's aggregated annotation occurrence. */
24
20
  export declare function addSelectionToConversation(input: ParentComposerInput, selection: ConversationSelection, comment?: string): boolean;
25
- /** Remove one unsent selected-passage annotation, retaining the aggregate when needed. */
21
+ export declare function removeConversationAnnotations(input: ParentComposerInput): boolean;
26
22
  export declare function removeConversationAnnotation(input: ParentComposerInput, annotationIndex: number): boolean;
27
- /** Replace the optional comment on one unsent selected-passage annotation. */
28
23
  export declare function updateConversationAnnotation(input: ParentComposerInput, annotationIndex: number, comment?: string): boolean;
29
- export interface ConversationAnnotationRecoveryRecord {
30
- readonly ref: string;
31
- readonly displayDraft: string;
32
- readonly mirrorDraft: string;
33
- readonly baseDraft: string;
34
- }
35
- /** Describe both the display draft and DSH's persisted clipboard projection. */
36
- export declare function conversationAnnotationRecoveryRecord(snapshot: ParentComposerInputSnapshot): ConversationAnnotationRecoveryRecord | undefined;
37
- /** Return the valid aggregated reference currently occupying the leading draft slot. */
38
- export declare function conversationAnnotationReference(snapshot: ParentComposerInputSnapshot): string | undefined;
39
- /** Remove the plugin-owned prefix when rc.6 restored its draft without occurrences. */
40
- export declare function removeOrphanedConversationAnnotationPlaceholder(input: ParentComposerInput): boolean;
41
- /** Rehydrate a lost occurrence over either its display draft or exact mirror projection. */
42
- export declare function restoreConversationAnnotationReference(input: ParentComposerInput, ref: string, expectedMirrorDraft?: string, expectedBaseDraft?: string): boolean;
24
+ export declare const selectionReferenceSource: SelectionReferenceSource;
43
25
  export interface AnnotatedConversationPrompt {
44
26
  readonly annotations: readonly ConversationAnnotation[];
45
27
  readonly message: string;
46
28
  }
47
- /** Parse the durable model form back into the user-facing annotation capsule. */
48
29
  export declare function parseAnnotatedConversationPrompt(text: string): AnnotatedConversationPrompt | undefined;
49
- /** Add the selected quote to the first child prompt. */
30
+ /** The first prompt uses the same durable annotation format as the main composer. */
50
31
  export declare function buildSideChatPrompt(selection: ConversationSelection | undefined, question: string): readonly SideChatPromptPart[];
32
+ /** Decode a question only after recognizing this plugin's annotation prefix. */
33
+ export declare function annotationMessageText(message: string): string;
34
+ /** One projection supplies both the annotation capsule and the visible question. */
35
+ export declare function parseSideChatPrompt(text: string): AnnotatedConversationPrompt | undefined;
36
+ export declare function sideChatQuestionText(text: string): string;