@ahggg/dsh-side-chat 0.7.1 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/lib/client/apply.d.ts +0 -1
- package/lib/client/apply.js +0 -1
- package/lib/client/contracts.d.ts +0 -1
- package/lib/client/contracts.js +0 -1
- package/lib/client/index.d.ts +0 -1
- package/lib/client/index.js +0 -1
- package/lib/client/model-preference.d.ts +0 -1
- package/lib/client/model-preference.js +0 -1
- package/lib/client/panel/SelectionQuote.d.ts +0 -1
- package/lib/client/panel/SelectionQuote.js +0 -1
- package/lib/client/panel/SendIcon.d.ts +0 -1
- package/lib/client/panel/SendIcon.js +0 -1
- package/lib/client/panel/SideChatBody.d.ts +0 -1
- package/lib/client/panel/SideChatBody.js +0 -1
- package/lib/client/panel/SideChatErrorState.d.ts +0 -1
- package/lib/client/panel/SideChatErrorState.js +0 -1
- package/lib/client/panel/SideChatHeader.d.ts +0 -1
- package/lib/client/panel/SideChatHeader.js +0 -1
- package/lib/client/panel/SideChatModelSelect.d.ts +0 -1
- package/lib/client/panel/SideChatModelSelect.js +0 -1
- package/lib/client/panel/SideChatPanel.d.ts +0 -1
- package/lib/client/panel/SideChatPanel.js +0 -1
- package/lib/client/panel/StopIcon.d.ts +0 -1
- package/lib/client/panel/StopIcon.js +0 -1
- package/lib/client/panel/messages.d.ts +0 -1
- package/lib/client/panel/messages.js +0 -1
- package/lib/client/panel/use-auto-growing-textarea.d.ts +0 -1
- package/lib/client/panel/use-auto-growing-textarea.js +0 -1
- package/lib/client/parent-composer/ParentConversationAnnotations.d.ts +0 -1
- package/lib/client/parent-composer/ParentConversationAnnotations.js +26 -3
- package/lib/client/parent-composer/add-to-conversation.d.ts +15 -54
- package/lib/client/parent-composer/add-to-conversation.js +308 -63
- package/lib/client/parent-composer/annotation-persistence.d.ts +1 -2
- package/lib/client/parent-composer/annotation-persistence.js +43 -41
- package/lib/client/parent-composer/composer-reference.d.ts +81 -0
- package/lib/client/parent-composer/composer-reference.js +89 -0
- package/lib/client/parent-composer/referenced-conversation.d.ts +2 -3
- package/lib/client/parent-composer/referenced-conversation.js +36 -25
- package/lib/client/rc6/ArchivedConversation.d.ts +2 -3
- package/lib/client/rc6/ArchivedConversation.js +6 -5
- package/lib/client/rc6/Rc6SideChatOverlay.d.ts +0 -1
- package/lib/client/rc6/Rc6SideChatOverlay.js +12 -2
- package/lib/client/rc6/SideChatTool.d.ts +1 -2
- package/lib/client/rc6/SideChatTool.js +18 -9
- package/lib/client/rc6/context.d.ts +9 -3
- package/lib/client/rc6/context.js +0 -1
- package/lib/client/rc6/primitive-labels.d.ts +7 -0
- package/lib/client/rc6/primitive-labels.js +60 -0
- package/lib/client/rc6/remote-adapter.d.ts +0 -1
- package/lib/client/rc6/remote-adapter.js +4 -3
- package/lib/client/rc6/runtime-compat.d.ts +120 -0
- package/lib/client/rc6/runtime-compat.js +187 -0
- package/lib/client/rc6/sessions-adapter.d.ts +9 -4
- package/lib/client/rc6/sessions-adapter.js +74 -46
- package/lib/client/selection/ConversationAnnotationMarkers.d.ts +0 -1
- package/lib/client/selection/ConversationAnnotationMarkers.js +0 -1
- package/lib/client/selection/SelectionActions.d.ts +5 -2
- package/lib/client/selection/SelectionActions.js +66 -50
- package/lib/client/selection/selection-controller.d.ts +0 -1
- package/lib/client/selection/selection-controller.js +0 -1
- package/lib/client/selection/selection-limits.d.ts +0 -1
- package/lib/client/selection/selection-limits.js +0 -1
- package/lib/client/selection/selection-normalizer.d.ts +0 -1
- package/lib/client/selection/selection-normalizer.js +0 -1
- package/lib/client/side-chat-controller.d.ts +0 -1
- package/lib/client/side-chat-controller.js +0 -1
- package/lib/client.js +1173 -8744
- package/lib/host/archived-fork-service.d.ts +0 -1
- package/lib/host/archived-fork-service.js +71 -26
- package/lib/host/index.d.ts +0 -1
- package/lib/host/index.js +0 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -1
- package/lib/remote.d.ts +0 -1
- package/lib/remote.js +0 -1
- package/lib/shared/archived-wire.d.ts +0 -1
- package/lib/shared/archived-wire.js +0 -1
- package/lib/shared/constants.d.ts +0 -1
- package/lib/shared/constants.js +0 -1
- package/lib/shared/contracts.d.ts +0 -1
- package/lib/shared/contracts.js +0 -1
- package/lib/shared/error-codes.d.ts +0 -1
- package/lib/shared/error-codes.js +0 -1
- package/lib/shared/observable.d.ts +0 -1
- package/lib/shared/observable.js +0 -1
- package/lib/typert.d.ts +0 -1
- package/lib/typert.js +64 -51
- package/package.json +60 -38
- package/lib/client/apply.d.ts.map +0 -1
- package/lib/client/apply.js.map +0 -1
- package/lib/client/contracts.d.ts.map +0 -1
- package/lib/client/contracts.js.map +0 -1
- package/lib/client/index.d.ts.map +0 -1
- package/lib/client/index.js.map +0 -1
- package/lib/client/model-preference.d.ts.map +0 -1
- package/lib/client/model-preference.js.map +0 -1
- package/lib/client/panel/SelectionQuote.d.ts.map +0 -1
- package/lib/client/panel/SelectionQuote.js.map +0 -1
- package/lib/client/panel/SendIcon.d.ts.map +0 -1
- package/lib/client/panel/SendIcon.js.map +0 -1
- package/lib/client/panel/SideChatBody.d.ts.map +0 -1
- package/lib/client/panel/SideChatBody.js.map +0 -1
- package/lib/client/panel/SideChatErrorState.d.ts.map +0 -1
- package/lib/client/panel/SideChatErrorState.js.map +0 -1
- package/lib/client/panel/SideChatHeader.d.ts.map +0 -1
- package/lib/client/panel/SideChatHeader.js.map +0 -1
- package/lib/client/panel/SideChatModelSelect.d.ts.map +0 -1
- package/lib/client/panel/SideChatModelSelect.js.map +0 -1
- package/lib/client/panel/SideChatPanel.d.ts.map +0 -1
- package/lib/client/panel/SideChatPanel.js.map +0 -1
- package/lib/client/panel/StopIcon.d.ts.map +0 -1
- package/lib/client/panel/StopIcon.js.map +0 -1
- package/lib/client/panel/messages.d.ts.map +0 -1
- package/lib/client/panel/messages.js.map +0 -1
- package/lib/client/panel/use-auto-growing-textarea.d.ts.map +0 -1
- package/lib/client/panel/use-auto-growing-textarea.js.map +0 -1
- package/lib/client/parent-composer/ParentConversationAnnotations.d.ts.map +0 -1
- package/lib/client/parent-composer/ParentConversationAnnotations.js.map +0 -1
- package/lib/client/parent-composer/add-to-conversation.d.ts.map +0 -1
- package/lib/client/parent-composer/add-to-conversation.js.map +0 -1
- package/lib/client/parent-composer/annotation-persistence.d.ts.map +0 -1
- package/lib/client/parent-composer/annotation-persistence.js.map +0 -1
- package/lib/client/parent-composer/referenced-conversation.d.ts.map +0 -1
- package/lib/client/parent-composer/referenced-conversation.js.map +0 -1
- package/lib/client/rc6/ArchivedConversation.d.ts.map +0 -1
- package/lib/client/rc6/ArchivedConversation.js.map +0 -1
- package/lib/client/rc6/Rc6SideChatOverlay.d.ts.map +0 -1
- package/lib/client/rc6/Rc6SideChatOverlay.js.map +0 -1
- package/lib/client/rc6/SideChatTool.d.ts.map +0 -1
- package/lib/client/rc6/SideChatTool.js.map +0 -1
- package/lib/client/rc6/context.d.ts.map +0 -1
- package/lib/client/rc6/context.js.map +0 -1
- package/lib/client/rc6/remote-adapter.d.ts.map +0 -1
- package/lib/client/rc6/remote-adapter.js.map +0 -1
- package/lib/client/rc6/sessions-adapter.d.ts.map +0 -1
- package/lib/client/rc6/sessions-adapter.js.map +0 -1
- package/lib/client/selection/ConversationAnnotationMarkers.d.ts.map +0 -1
- package/lib/client/selection/ConversationAnnotationMarkers.js.map +0 -1
- package/lib/client/selection/SelectionActions.d.ts.map +0 -1
- package/lib/client/selection/SelectionActions.js.map +0 -1
- package/lib/client/selection/selection-controller.d.ts.map +0 -1
- package/lib/client/selection/selection-controller.js.map +0 -1
- package/lib/client/selection/selection-limits.d.ts.map +0 -1
- package/lib/client/selection/selection-limits.js.map +0 -1
- package/lib/client/selection/selection-normalizer.d.ts.map +0 -1
- package/lib/client/selection/selection-normalizer.js.map +0 -1
- package/lib/client/side-chat-controller.d.ts.map +0 -1
- package/lib/client/side-chat-controller.js.map +0 -1
- package/lib/client.js.map +0 -1
- package/lib/host/archived-fork-service.d.ts.map +0 -1
- package/lib/host/archived-fork-service.js.map +0 -1
- package/lib/host/index.d.ts.map +0 -1
- package/lib/host/index.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/remote.d.ts.map +0 -1
- package/lib/remote.js.map +0 -1
- package/lib/shared/archived-wire.d.ts.map +0 -1
- package/lib/shared/archived-wire.js.map +0 -1
- package/lib/shared/constants.d.ts.map +0 -1
- package/lib/shared/constants.js.map +0 -1
- package/lib/shared/contracts.d.ts.map +0 -1
- package/lib/shared/contracts.js.map +0 -1
- package/lib/shared/error-codes.d.ts.map +0 -1
- package/lib/shared/error-codes.js.map +0 -1
- package/lib/shared/observable.d.ts.map +0 -1
- package/lib/shared/observable.js.map +0 -1
- package/lib/typert.d.ts.map +0 -1
- package/lib/typert.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { draftWithoutOccurrence, LEGACY_REFERENCE_PLACEHOLDER, newlyInsertedOccurrence, occurrenceEditSpan, occurrenceMatchesDraft, occurrenceRange, referenceDisplayText, } from './composer-reference.js';
|
|
1
2
|
const SELECTION_REFERENCE_SOURCE = 'dsh-side-chat-selection';
|
|
2
|
-
const OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
|
|
3
3
|
export const SELECTION_REFERENCE_LABEL = '__dsh_side_chat_annotations__';
|
|
4
4
|
function escapeXmlText(value) {
|
|
5
5
|
return value
|
|
@@ -113,13 +113,6 @@ function selectedContext(annotations) {
|
|
|
113
113
|
function selectionOccurrences(snapshot) {
|
|
114
114
|
return snapshot.occurrences.filter(occurrence => occurrence.source === SELECTION_REFERENCE_SOURCE);
|
|
115
115
|
}
|
|
116
|
-
function occurrenceLength(occurrence) {
|
|
117
|
-
return occurrence.length !== undefined
|
|
118
|
-
&& Number.isSafeInteger(occurrence.length)
|
|
119
|
-
&& occurrence.length > 0
|
|
120
|
-
? occurrence.length
|
|
121
|
-
: 1;
|
|
122
|
-
}
|
|
123
116
|
function storedConversationAnnotations(snapshot) {
|
|
124
117
|
return selectionOccurrences(snapshot).flatMap((occurrence) => {
|
|
125
118
|
try {
|
|
@@ -150,16 +143,19 @@ export function conversationSelectionAnnotations(snapshot) {
|
|
|
150
143
|
}
|
|
151
144
|
function draftWithoutSelectionOccurrences(snapshot) {
|
|
152
145
|
const occurrences = [...selectionOccurrences(snapshot)].sort((a, b) => b.offset - a.offset);
|
|
153
|
-
let
|
|
146
|
+
let current = snapshot;
|
|
154
147
|
for (const occurrence of occurrences) {
|
|
155
|
-
draft =
|
|
156
|
-
|
|
148
|
+
const draft = draftWithoutOccurrence(current, occurrence, SELECTION_REFERENCE_LABEL);
|
|
149
|
+
if (draft === undefined)
|
|
150
|
+
continue;
|
|
151
|
+
current = { ...current, draft };
|
|
157
152
|
}
|
|
158
|
-
|
|
153
|
+
let draft = current.draft;
|
|
154
|
+
// Add to chat owns the separator immediately after its leading occurrence.
|
|
159
155
|
if (occurrences.some(occurrence => occurrence.offset === 0)) {
|
|
160
156
|
if (draft.startsWith('\n\n'))
|
|
161
157
|
draft = draft.slice(2);
|
|
162
|
-
else if (draft.startsWith('\n'))
|
|
158
|
+
else if (draft.startsWith('\n') || draft.startsWith(' '))
|
|
163
159
|
draft = draft.slice(1);
|
|
164
160
|
}
|
|
165
161
|
return draft;
|
|
@@ -167,8 +163,23 @@ function draftWithoutSelectionOccurrences(snapshot) {
|
|
|
167
163
|
/** Remove every unsent plugin annotation while preserving the user's draft. */
|
|
168
164
|
export function removeConversationAnnotations(input) {
|
|
169
165
|
const snapshot = input.state.getSnapshot();
|
|
170
|
-
|
|
166
|
+
const occurrences = selectionOccurrences(snapshot);
|
|
167
|
+
if (occurrences.length === 0)
|
|
171
168
|
return false;
|
|
169
|
+
if (input.referenceMode === 'lexical' && input.replaceText !== undefined) {
|
|
170
|
+
for (const occurrenceId of occurrences.map(occurrence => occurrence.occurrenceId).reverse()) {
|
|
171
|
+
const current = input.state.getSnapshot();
|
|
172
|
+
const occurrence = current.occurrences.find(candidate => candidate.occurrenceId === occurrenceId);
|
|
173
|
+
if (occurrence === undefined)
|
|
174
|
+
continue;
|
|
175
|
+
const span = occurrenceEditSpan(input, current, occurrence, {
|
|
176
|
+
consumeFollowingSeparator: true,
|
|
177
|
+
});
|
|
178
|
+
if (span === undefined || !input.replaceText('', span))
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
return selectionOccurrences(input.state.getSnapshot()).length === 0;
|
|
182
|
+
}
|
|
172
183
|
input.setDraft(draftWithoutSelectionOccurrences(snapshot));
|
|
173
184
|
return true;
|
|
174
185
|
}
|
|
@@ -188,46 +199,169 @@ export const selectionReferenceSource = {
|
|
|
188
199
|
},
|
|
189
200
|
},
|
|
190
201
|
};
|
|
191
|
-
function
|
|
192
|
-
return annotations.map(annotation => annotation.text).join('\n\n');
|
|
193
|
-
}
|
|
194
|
-
function writeConversationAnnotations(input, before, annotations) {
|
|
202
|
+
function writeTextConversationAnnotations(input, before, annotations) {
|
|
195
203
|
const draft = draftWithoutSelectionOccurrences(before);
|
|
196
|
-
if (selectionOccurrences(before).length > 0)
|
|
197
|
-
input.setDraft(draft);
|
|
198
204
|
const insertionState = input.state.getSnapshot();
|
|
205
|
+
if (insertionState.draftRev !== before.draftRev || insertionState.draft !== before.draft)
|
|
206
|
+
return false;
|
|
199
207
|
const ref = encodeSelectionReference(annotations);
|
|
200
208
|
const inserted = input.insertReference({
|
|
201
209
|
source: SELECTION_REFERENCE_SOURCE,
|
|
202
210
|
ref,
|
|
203
211
|
label: SELECTION_REFERENCE_LABEL,
|
|
204
|
-
clipboardText:
|
|
212
|
+
clipboardText: annotations.map(annotation => annotation.text).join('\n\n'),
|
|
205
213
|
}, {
|
|
206
214
|
start: 0,
|
|
207
215
|
end: 0,
|
|
208
216
|
draftRev: insertionState.draftRev,
|
|
209
217
|
});
|
|
218
|
+
// Existing annotations are intentionally still live here. If DSH rejects the
|
|
219
|
+
// insertion, the operation is a no-op instead of deleting the previous chip.
|
|
210
220
|
if (!inserted)
|
|
211
221
|
return false;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
+
const afterInsert = input.state.getSnapshot();
|
|
223
|
+
const occurrence = newlyInsertedOccurrence(insertionState, afterInsert, SELECTION_REFERENCE_SOURCE, ref);
|
|
224
|
+
const rollbackInsertion = () => {
|
|
225
|
+
const current = input.state.getSnapshot();
|
|
226
|
+
const insertedOccurrence = occurrence === undefined
|
|
227
|
+
? undefined
|
|
228
|
+
: current.occurrences.find(candidate => candidate.occurrenceId === occurrence.occurrenceId);
|
|
229
|
+
if (insertedOccurrence === undefined || insertedOccurrence.offset !== 0)
|
|
230
|
+
return;
|
|
231
|
+
const insertedRange = occurrenceRange(current, insertedOccurrence, SELECTION_REFERENCE_LABEL);
|
|
232
|
+
if (insertedRange === undefined || insertedRange.start !== 0)
|
|
233
|
+
return;
|
|
234
|
+
let tail = current.draft.slice(insertedRange.end);
|
|
235
|
+
if (tail.startsWith(' ') && tail.slice(1) === before.draft)
|
|
236
|
+
tail = before.draft;
|
|
237
|
+
else if (tail !== before.draft)
|
|
238
|
+
return;
|
|
239
|
+
input.setDraft(tail);
|
|
240
|
+
};
|
|
241
|
+
if (occurrence === undefined || occurrence.offset !== 0) {
|
|
242
|
+
rollbackInsertion();
|
|
222
243
|
return false;
|
|
223
244
|
}
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
245
|
+
const range = occurrenceRange(afterInsert, occurrence, SELECTION_REFERENCE_LABEL);
|
|
246
|
+
if (range === undefined || range.start !== 0) {
|
|
247
|
+
rollbackInsertion();
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
// Current DSH inserts the complete `@label` display text followed by an
|
|
251
|
+
// optional ASCII separating gap. Older releases insert a one-unit U+FFFC
|
|
252
|
+
// placeholder. At this point the old occurrence is still in the tail; one
|
|
253
|
+
// tail replacement preserves the new occurrence and removes the old one.
|
|
254
|
+
let rest = afterInsert.draft.slice(range.end);
|
|
255
|
+
if (rest.startsWith(' ') && rest.slice(1) === before.draft)
|
|
256
|
+
rest = before.draft;
|
|
257
|
+
else if (rest !== before.draft) {
|
|
258
|
+
rollbackInsertion();
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
const display = afterInsert.draft.slice(range.start, range.end);
|
|
262
|
+
const normalizedDraft = `${display}\n\n${draft}`;
|
|
263
|
+
// insertReference and setDraft are separate public transactions. Re-read the
|
|
264
|
+
// exact occurrence before the second write so a synchronous subscriber cannot
|
|
265
|
+
// make us normalize a stale insertion.
|
|
266
|
+
const current = input.state.getSnapshot();
|
|
267
|
+
const currentOccurrence = current.occurrences.find(candidate => candidate.occurrenceId === occurrence.occurrenceId);
|
|
268
|
+
if (current.draftRev !== afterInsert.draftRev
|
|
269
|
+
|| current.draft !== afterInsert.draft
|
|
270
|
+
|| currentOccurrence === undefined
|
|
271
|
+
|| currentOccurrence.source !== SELECTION_REFERENCE_SOURCE
|
|
272
|
+
|| currentOccurrence.ref !== ref
|
|
273
|
+
|| !occurrenceMatchesDraft(current, currentOccurrence, SELECTION_REFERENCE_LABEL)) {
|
|
274
|
+
rollbackInsertion();
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
input.setDraft(normalizedDraft);
|
|
278
|
+
const normalized = input.state.getSnapshot();
|
|
279
|
+
const retained = normalized.occurrences.find(candidate => candidate.occurrenceId === occurrence.occurrenceId);
|
|
280
|
+
return normalized.draft === normalizedDraft
|
|
281
|
+
&& retained !== undefined
|
|
282
|
+
&& retained.source === SELECTION_REFERENCE_SOURCE
|
|
283
|
+
&& retained.ref === ref
|
|
284
|
+
&& retained.offset === 0
|
|
285
|
+
&& occurrenceMatchesDraft(normalized, retained, SELECTION_REFERENCE_LABEL);
|
|
286
|
+
}
|
|
287
|
+
function writeLexicalConversationAnnotations(input, before, annotations) {
|
|
288
|
+
if (input.replaceText === undefined)
|
|
289
|
+
return false;
|
|
290
|
+
const existing = selectionOccurrences(before);
|
|
291
|
+
if (existing.length > 1)
|
|
292
|
+
return false;
|
|
293
|
+
const previous = existing[0];
|
|
294
|
+
if (previous !== undefined && previous.length === undefined)
|
|
295
|
+
return false;
|
|
296
|
+
let insertionState = before;
|
|
297
|
+
let preparedGap = false;
|
|
298
|
+
if (previous === undefined) {
|
|
299
|
+
// A gap inserted before the chip is unambiguously plugin-owned. DSH's
|
|
300
|
+
// automatic gap cannot otherwise be distinguished from a user's leading
|
|
301
|
+
// space when the reference is later removed.
|
|
302
|
+
if (!input.replaceText(' ', { start: 0, end: 0, draftRev: before.draftRev }))
|
|
303
|
+
return false;
|
|
304
|
+
insertionState = input.state.getSnapshot();
|
|
305
|
+
if (insertionState.draft !== ` ${before.draft}`)
|
|
306
|
+
return false;
|
|
307
|
+
preparedGap = true;
|
|
308
|
+
}
|
|
309
|
+
const span = previous === undefined
|
|
310
|
+
? { start: 0, end: 0, draftRev: insertionState.draftRev }
|
|
311
|
+
: occurrenceEditSpan(input, insertionState, previous);
|
|
312
|
+
if (span === undefined)
|
|
313
|
+
return false;
|
|
314
|
+
const ref = encodeSelectionReference(annotations);
|
|
315
|
+
const clipboardText = annotations.map(annotation => annotation.text).join('\n\n');
|
|
316
|
+
const inserted = input.insertReference({
|
|
317
|
+
source: SELECTION_REFERENCE_SOURCE,
|
|
318
|
+
ref,
|
|
319
|
+
label: SELECTION_REFERENCE_LABEL,
|
|
320
|
+
clipboardText,
|
|
321
|
+
}, span);
|
|
322
|
+
if (!inserted) {
|
|
323
|
+
if (preparedGap) {
|
|
324
|
+
const current = input.state.getSnapshot();
|
|
325
|
+
if (current.draft === ` ${before.draft}`) {
|
|
326
|
+
input.replaceText('', { start: 0, end: 1, draftRev: current.draftRev });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
const afterInsert = input.state.getSnapshot();
|
|
332
|
+
const occurrence = newlyInsertedOccurrence(insertionState, afterInsert, SELECTION_REFERENCE_SOURCE, ref);
|
|
333
|
+
const previousEnd = previous === undefined ? 0 : previous.offset + (previous.length ?? 0);
|
|
334
|
+
const previousTail = previous === undefined
|
|
335
|
+
? insertionState.draft
|
|
336
|
+
: insertionState.draft.slice(previousEnd);
|
|
337
|
+
const automaticGap = previousTail.startsWith(' ') ? '' : ' ';
|
|
338
|
+
const expectedDraft = previous === undefined
|
|
339
|
+
? `${clipboardText}${insertionState.draft}`
|
|
340
|
+
: insertionState.draft.slice(0, previous.offset) + clipboardText + automaticGap + previousTail;
|
|
341
|
+
const valid = occurrence !== undefined
|
|
342
|
+
&& selectionOccurrences(afterInsert).length === 1
|
|
343
|
+
&& occurrence.offset === (previous?.offset ?? 0)
|
|
344
|
+
&& occurrence.label === SELECTION_REFERENCE_LABEL
|
|
345
|
+
&& occurrence.clipboardText === clipboardText
|
|
346
|
+
&& occurrenceMatchesDraft(afterInsert, occurrence, SELECTION_REFERENCE_LABEL)
|
|
347
|
+
&& afterInsert.draft === expectedDraft;
|
|
348
|
+
if (valid)
|
|
349
|
+
return true;
|
|
350
|
+
// Best-effort rollback for a first insertion. Replacing an existing chip is
|
|
351
|
+
// atomic and should never reach this path on a conforming DSH input facade.
|
|
352
|
+
if (previous === undefined && occurrence !== undefined) {
|
|
353
|
+
const rollback = occurrenceEditSpan(input, afterInsert, occurrence, {
|
|
354
|
+
consumeFollowingSeparator: true,
|
|
355
|
+
});
|
|
356
|
+
if (rollback !== undefined)
|
|
357
|
+
input.replaceText('', rollback);
|
|
358
|
+
}
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
function writeConversationAnnotations(input, before, annotations) {
|
|
362
|
+
return input.referenceMode === 'lexical'
|
|
363
|
+
? writeLexicalConversationAnnotations(input, before, annotations)
|
|
364
|
+
: writeTextConversationAnnotations(input, before, annotations);
|
|
231
365
|
}
|
|
232
366
|
/** Add one passage to the parent composer's aggregated annotation occurrence. */
|
|
233
367
|
export function addSelectionToConversation(input, selection, comment) {
|
|
@@ -235,6 +369,17 @@ export function addSelectionToConversation(input, selection, comment) {
|
|
|
235
369
|
const annotations = [...storedConversationAnnotations(before), annotationFromSelection(selection, comment)];
|
|
236
370
|
return writeConversationAnnotations(input, before, annotations);
|
|
237
371
|
}
|
|
372
|
+
/** Remove one unsent selected-passage annotation, retaining the aggregate when needed. */
|
|
373
|
+
export function removeConversationAnnotation(input, annotationIndex) {
|
|
374
|
+
const before = input.state.getSnapshot();
|
|
375
|
+
const annotations = [...storedConversationAnnotations(before)];
|
|
376
|
+
if (!Number.isSafeInteger(annotationIndex) || annotations[annotationIndex] === undefined)
|
|
377
|
+
return false;
|
|
378
|
+
annotations.splice(annotationIndex, 1);
|
|
379
|
+
if (annotations.length === 0)
|
|
380
|
+
return removeConversationAnnotations(input);
|
|
381
|
+
return writeConversationAnnotations(input, before, annotations);
|
|
382
|
+
}
|
|
238
383
|
/** Replace the optional comment on one unsent selected-passage annotation. */
|
|
239
384
|
export function updateConversationAnnotation(input, annotationIndex, comment) {
|
|
240
385
|
const before = input.state.getSnapshot();
|
|
@@ -252,13 +397,90 @@ export function updateConversationAnnotation(input, annotationIndex, comment) {
|
|
|
252
397
|
};
|
|
253
398
|
return writeConversationAnnotations(input, before, annotations);
|
|
254
399
|
}
|
|
400
|
+
function exactClipboardProjection(snapshot, annotationOccurrence, annotationClipboardText) {
|
|
401
|
+
let projected = '';
|
|
402
|
+
let cursor = 0;
|
|
403
|
+
const occurrences = [...snapshot.occurrences].sort((left, right) => left.offset - right.offset);
|
|
404
|
+
for (const occurrence of occurrences) {
|
|
405
|
+
const expectedLabel = occurrence.occurrenceId === annotationOccurrence.occurrenceId
|
|
406
|
+
? SELECTION_REFERENCE_LABEL
|
|
407
|
+
: occurrence.label;
|
|
408
|
+
if (occurrence.length === undefined
|
|
409
|
+
&& !snapshot.draft.startsWith(LEGACY_REFERENCE_PLACEHOLDER, occurrence.offset)
|
|
410
|
+
&& expectedLabel === undefined)
|
|
411
|
+
return;
|
|
412
|
+
const range = occurrenceRange(snapshot, occurrence, expectedLabel ?? '');
|
|
413
|
+
if (range === undefined || range.start < cursor)
|
|
414
|
+
return;
|
|
415
|
+
const clipboardText = occurrence.occurrenceId === annotationOccurrence.occurrenceId
|
|
416
|
+
? annotationClipboardText
|
|
417
|
+
: occurrence.clipboardText;
|
|
418
|
+
if (clipboardText === undefined)
|
|
419
|
+
return;
|
|
420
|
+
projected += snapshot.draft.slice(cursor, range.start) + clipboardText;
|
|
421
|
+
cursor = range.end;
|
|
422
|
+
}
|
|
423
|
+
return projected + snapshot.draft.slice(cursor);
|
|
424
|
+
}
|
|
425
|
+
function annotationSeparator(draftTail) {
|
|
426
|
+
if (draftTail.startsWith('\n\n'))
|
|
427
|
+
return '\n\n';
|
|
428
|
+
if (draftTail.startsWith('\n'))
|
|
429
|
+
return '\n';
|
|
430
|
+
if (draftTail.startsWith(' '))
|
|
431
|
+
return ' ';
|
|
432
|
+
return '';
|
|
433
|
+
}
|
|
434
|
+
function baseDraftFromMirror(mirrorDraft, clipboardText, expectedBaseDraft) {
|
|
435
|
+
const separators = ['\n\n', '\n', ' ', ''];
|
|
436
|
+
if (expectedBaseDraft !== undefined) {
|
|
437
|
+
return separators.some(separator => mirrorDraft === clipboardText + separator + expectedBaseDraft)
|
|
438
|
+
? expectedBaseDraft
|
|
439
|
+
: undefined;
|
|
440
|
+
}
|
|
441
|
+
for (const separator of separators.slice(0, -1)) {
|
|
442
|
+
const prefix = clipboardText + separator;
|
|
443
|
+
if (mirrorDraft.startsWith(prefix))
|
|
444
|
+
return mirrorDraft.slice(prefix.length);
|
|
445
|
+
}
|
|
446
|
+
return mirrorDraft === clipboardText ? '' : undefined;
|
|
447
|
+
}
|
|
448
|
+
/** Describe both the display draft and DSH's persisted clipboard projection. */
|
|
449
|
+
export function conversationAnnotationRecoveryRecord(snapshot) {
|
|
450
|
+
const occurrence = selectionOccurrences(snapshot).find(candidate => candidate.offset === 0);
|
|
451
|
+
const ref = conversationAnnotationReference(snapshot);
|
|
452
|
+
if (occurrence === undefined || ref === undefined)
|
|
453
|
+
return;
|
|
454
|
+
const range = occurrenceRange(snapshot, occurrence, SELECTION_REFERENCE_LABEL);
|
|
455
|
+
if (range === undefined || range.start !== 0)
|
|
456
|
+
return;
|
|
457
|
+
let annotations;
|
|
458
|
+
try {
|
|
459
|
+
annotations = decodeStoredSelectionReference(ref);
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const clipboardText = annotations.map(annotation => annotation.text).join('\n\n');
|
|
465
|
+
const mirrorDraft = exactClipboardProjection(snapshot, occurrence, clipboardText);
|
|
466
|
+
if (mirrorDraft === undefined)
|
|
467
|
+
return;
|
|
468
|
+
const separator = annotationSeparator(snapshot.draft.slice(range.end));
|
|
469
|
+
const mirrorPrefix = clipboardText + separator;
|
|
470
|
+
if (!mirrorDraft.startsWith(mirrorPrefix))
|
|
471
|
+
return;
|
|
472
|
+
return {
|
|
473
|
+
ref,
|
|
474
|
+
displayDraft: snapshot.draft,
|
|
475
|
+
mirrorDraft,
|
|
476
|
+
baseDraft: mirrorDraft.slice(mirrorPrefix.length),
|
|
477
|
+
};
|
|
478
|
+
}
|
|
255
479
|
/** Return the valid aggregated reference currently occupying the leading draft slot. */
|
|
256
480
|
export function conversationAnnotationReference(snapshot) {
|
|
257
481
|
const occurrence = selectionOccurrences(snapshot).find(candidate => candidate.offset === 0);
|
|
258
482
|
if (occurrence === undefined
|
|
259
|
-
||
|
|
260
|
-
|| (occurrence.length === undefined
|
|
261
|
-
&& !snapshot.draft.startsWith(OBJECT_REPLACEMENT_CHARACTER)))
|
|
483
|
+
|| !occurrenceMatchesDraft(snapshot, occurrence, SELECTION_REFERENCE_LABEL))
|
|
262
484
|
return;
|
|
263
485
|
try {
|
|
264
486
|
decodeStoredSelectionReference(occurrence.ref);
|
|
@@ -268,25 +490,25 @@ export function conversationAnnotationReference(snapshot) {
|
|
|
268
490
|
return;
|
|
269
491
|
}
|
|
270
492
|
}
|
|
271
|
-
function draftWithoutOrphanedAnnotationPrefix(snapshot
|
|
493
|
+
function draftWithoutOrphanedAnnotationPrefix(snapshot) {
|
|
272
494
|
if (snapshot.occurrences.some(occurrence => occurrence.offset === 0))
|
|
273
495
|
return;
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
return
|
|
496
|
+
const legacyPrefix = `${LEGACY_REFERENCE_PLACEHOLDER}\n\n`;
|
|
497
|
+
if (snapshot.draft.startsWith(legacyPrefix))
|
|
498
|
+
return snapshot.draft.slice(legacyPrefix.length);
|
|
499
|
+
const display = referenceDisplayText(SELECTION_REFERENCE_LABEL);
|
|
500
|
+
if (!snapshot.draft.startsWith(display))
|
|
501
|
+
return;
|
|
502
|
+
let rest = snapshot.draft.slice(display.length);
|
|
503
|
+
if (rest.startsWith('\n\n'))
|
|
504
|
+
rest = rest.slice(2);
|
|
505
|
+
else if (rest.startsWith(' '))
|
|
506
|
+
rest = rest.slice(1);
|
|
507
|
+
else
|
|
508
|
+
return;
|
|
509
|
+
return rest;
|
|
288
510
|
}
|
|
289
|
-
/** Remove
|
|
511
|
+
/** Remove the plugin-owned prefix when rc.6 restored its draft without occurrences. */
|
|
290
512
|
export function removeOrphanedConversationAnnotationPlaceholder(input) {
|
|
291
513
|
const draft = draftWithoutOrphanedAnnotationPrefix(input.state.getSnapshot());
|
|
292
514
|
if (draft === undefined)
|
|
@@ -294,8 +516,8 @@ export function removeOrphanedConversationAnnotationPlaceholder(input) {
|
|
|
294
516
|
input.setDraft(draft);
|
|
295
517
|
return true;
|
|
296
518
|
}
|
|
297
|
-
/** Rehydrate a lost occurrence
|
|
298
|
-
export function restoreConversationAnnotationReference(input, ref) {
|
|
519
|
+
/** Rehydrate a lost occurrence over either its display draft or exact mirror projection. */
|
|
520
|
+
export function restoreConversationAnnotationReference(input, ref, expectedMirrorDraft, expectedBaseDraft) {
|
|
299
521
|
let annotations;
|
|
300
522
|
try {
|
|
301
523
|
annotations = decodeStoredSelectionReference(ref);
|
|
@@ -303,11 +525,35 @@ export function restoreConversationAnnotationReference(input, ref) {
|
|
|
303
525
|
catch {
|
|
304
526
|
return false;
|
|
305
527
|
}
|
|
306
|
-
const
|
|
528
|
+
const snapshot = input.state.getSnapshot();
|
|
529
|
+
const orphanDraft = draftWithoutOrphanedAnnotationPrefix(snapshot);
|
|
530
|
+
const clipboardText = annotations.map(annotation => annotation.text).join('\n\n');
|
|
531
|
+
const mirrorBaseDraft = expectedMirrorDraft === undefined
|
|
532
|
+
? undefined
|
|
533
|
+
: baseDraftFromMirror(expectedMirrorDraft, clipboardText, expectedBaseDraft);
|
|
534
|
+
const draft = orphanDraft
|
|
535
|
+
?? (expectedMirrorDraft !== undefined
|
|
536
|
+
&& snapshot.occurrences.length === 0
|
|
537
|
+
&& snapshot.draft === expectedMirrorDraft
|
|
538
|
+
&& mirrorBaseDraft !== undefined
|
|
539
|
+
? mirrorBaseDraft
|
|
540
|
+
: undefined);
|
|
307
541
|
if (draft === undefined)
|
|
308
542
|
return false;
|
|
309
|
-
|
|
310
|
-
|
|
543
|
+
if (draft !== snapshot.draft)
|
|
544
|
+
input.setDraft(draft);
|
|
545
|
+
const beforeWrite = input.state.getSnapshot();
|
|
546
|
+
if (beforeWrite.draft !== draft || beforeWrite.occurrences.length !== 0)
|
|
547
|
+
return false;
|
|
548
|
+
if (writeConversationAnnotations(input, beforeWrite, annotations))
|
|
549
|
+
return true;
|
|
550
|
+
// Recovery must be content preserving. If DSH rejects insertion, put the
|
|
551
|
+
// exact mirror projection back rather than leaving a partially stripped draft.
|
|
552
|
+
const afterFailure = input.state.getSnapshot();
|
|
553
|
+
if (afterFailure.occurrences.length === 0 && expectedMirrorDraft !== undefined) {
|
|
554
|
+
input.setDraft(expectedMirrorDraft);
|
|
555
|
+
}
|
|
556
|
+
return false;
|
|
311
557
|
}
|
|
312
558
|
function unescapeXmlText(value) {
|
|
313
559
|
return value
|
|
@@ -371,4 +617,3 @@ export function buildSideChatPrompt(selection, question) {
|
|
|
371
617
|
].join('\n'),
|
|
372
618
|
}];
|
|
373
619
|
}
|
|
374
|
-
//# sourceMappingURL=add-to-conversation.js.map
|
|
@@ -5,7 +5,7 @@ interface AnnotationStorage {
|
|
|
5
5
|
setItem(key: string, value: string): void;
|
|
6
6
|
removeItem(key: string): void;
|
|
7
7
|
}
|
|
8
|
-
/** Tab-scoped recovery for drafts persisted
|
|
8
|
+
/** Tab-scoped recovery for drafts persisted as a reference clipboard projection. */
|
|
9
9
|
export declare class ConversationAnnotationPersistence {
|
|
10
10
|
private readonly storage;
|
|
11
11
|
private readonly observedSessions;
|
|
@@ -16,4 +16,3 @@ export declare class ConversationAnnotationPersistence {
|
|
|
16
16
|
private remove;
|
|
17
17
|
}
|
|
18
18
|
export {};
|
|
19
|
-
//# sourceMappingURL=annotation-persistence.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { conversationAnnotationRecoveryRecord, removeOrphanedConversationAnnotationPlaceholder, restoreConversationAnnotationReference, } from './add-to-conversation.js';
|
|
2
2
|
const STORAGE_PREFIX = 'dsh-side-chat:composer-annotations:';
|
|
3
3
|
function browserSessionStorage() {
|
|
4
4
|
if (typeof window === 'undefined')
|
|
@@ -13,37 +13,17 @@ function browserSessionStorage() {
|
|
|
13
13
|
function storageKey(sessionId) {
|
|
14
14
|
return `${STORAGE_PREFIX}${encodeURIComponent(sessionId)}`;
|
|
15
15
|
}
|
|
16
|
-
function clipboardProjection(snapshot) {
|
|
17
|
-
let draft = snapshot.draft;
|
|
18
|
-
const occurrences = [...snapshot.occurrences].sort((a, b) => b.offset - a.offset);
|
|
19
|
-
for (const occurrence of occurrences) {
|
|
20
|
-
const length = occurrence.length ?? 1;
|
|
21
|
-
if (!Number.isSafeInteger(occurrence.offset)
|
|
22
|
-
|| occurrence.offset < 0
|
|
23
|
-
|| !Number.isSafeInteger(length)
|
|
24
|
-
|| length <= 0
|
|
25
|
-
|| occurrence.offset + length > draft.length
|
|
26
|
-
|| occurrence.clipboardText === undefined)
|
|
27
|
-
return;
|
|
28
|
-
draft = draft.slice(0, occurrence.offset)
|
|
29
|
-
+ occurrence.clipboardText
|
|
30
|
-
+ draft.slice(occurrence.offset + length);
|
|
31
|
-
}
|
|
32
|
-
return draft;
|
|
33
|
-
}
|
|
34
16
|
function recordOf(snapshot) {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
return;
|
|
38
|
-
const projection = clipboardProjection(snapshot);
|
|
39
|
-
return {
|
|
40
|
-
version: 1,
|
|
41
|
-
draft: snapshot.draft,
|
|
42
|
-
...(projection === undefined ? {} : { projection }),
|
|
43
|
-
ref,
|
|
44
|
-
};
|
|
17
|
+
const record = conversationAnnotationRecoveryRecord(snapshot);
|
|
18
|
+
return record === undefined ? undefined : { version: 2, ...record };
|
|
45
19
|
}
|
|
46
|
-
|
|
20
|
+
function displayDraft(record) {
|
|
21
|
+
return record.version === 1 ? record.draft : record.displayDraft;
|
|
22
|
+
}
|
|
23
|
+
function mirrorDraft(record) {
|
|
24
|
+
return record.version === 1 ? record.projection : record.mirrorDraft;
|
|
25
|
+
}
|
|
26
|
+
/** Tab-scoped recovery for drafts persisted as a reference clipboard projection. */
|
|
47
27
|
export class ConversationAnnotationPersistence {
|
|
48
28
|
storage;
|
|
49
29
|
observedSessions = new Set();
|
|
@@ -60,14 +40,24 @@ export class ConversationAnnotationPersistence {
|
|
|
60
40
|
return;
|
|
61
41
|
}
|
|
62
42
|
const stored = this.read(key);
|
|
63
|
-
if (stored !== undefined
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
43
|
+
if (stored !== undefined) {
|
|
44
|
+
const exactDisplayDraft = snapshot.draft === displayDraft(stored);
|
|
45
|
+
const storedMirrorDraft = mirrorDraft(stored);
|
|
46
|
+
const exactMirrorDraft = storedMirrorDraft !== undefined && snapshot.draft === storedMirrorDraft;
|
|
47
|
+
if (exactDisplayDraft || exactMirrorDraft) {
|
|
48
|
+
this.observedSessions.add(key);
|
|
49
|
+
const restored = restoreConversationAnnotationReference(input, stored.ref, storedMirrorDraft, stored.version === 2 ? stored.baseDraft : undefined);
|
|
50
|
+
if (restored)
|
|
51
|
+
return;
|
|
52
|
+
// A refused insertion may move an exact display record to its exact
|
|
53
|
+
// mirror. Keep either v1 or v2 storage retryable from that safe state.
|
|
54
|
+
if (storedMirrorDraft !== undefined
|
|
55
|
+
&& input.state.getSnapshot().draft === storedMirrorDraft)
|
|
56
|
+
return;
|
|
57
|
+
this.remove(key);
|
|
58
|
+
removeOrphanedConversationAnnotationPlaceholder(input);
|
|
67
59
|
return;
|
|
68
|
-
|
|
69
|
-
removeOrphanedConversationAnnotationPlaceholder(input);
|
|
70
|
-
return;
|
|
60
|
+
}
|
|
71
61
|
}
|
|
72
62
|
// A live occurrence disappeared in this plugin lifetime: removal or send
|
|
73
63
|
// is authoritative, including the transition to an empty draft.
|
|
@@ -92,15 +82,28 @@ export class ConversationAnnotationPersistence {
|
|
|
92
82
|
if (raw === null)
|
|
93
83
|
return;
|
|
94
84
|
const value = JSON.parse(raw);
|
|
95
|
-
|
|
85
|
+
if (value.version === 1
|
|
96
86
|
&& typeof value.draft === 'string'
|
|
97
87
|
&& typeof value.ref === 'string'
|
|
98
|
-
&& (value.projection === undefined || typeof value.projection === 'string')
|
|
99
|
-
|
|
88
|
+
&& (value.projection === undefined || typeof value.projection === 'string')) {
|
|
89
|
+
return {
|
|
100
90
|
version: 1,
|
|
101
91
|
draft: value.draft,
|
|
102
92
|
...(value.projection === undefined ? {} : { projection: value.projection }),
|
|
103
93
|
ref: value.ref,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return value.version === 2
|
|
97
|
+
&& typeof value.displayDraft === 'string'
|
|
98
|
+
&& typeof value.mirrorDraft === 'string'
|
|
99
|
+
&& typeof value.baseDraft === 'string'
|
|
100
|
+
&& typeof value.ref === 'string'
|
|
101
|
+
? {
|
|
102
|
+
version: 2,
|
|
103
|
+
displayDraft: value.displayDraft,
|
|
104
|
+
mirrorDraft: value.mirrorDraft,
|
|
105
|
+
baseDraft: value.baseDraft,
|
|
106
|
+
ref: value.ref,
|
|
104
107
|
}
|
|
105
108
|
: undefined;
|
|
106
109
|
}
|
|
@@ -129,4 +132,3 @@ export class ConversationAnnotationPersistence {
|
|
|
129
132
|
}
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
|
-
//# sourceMappingURL=annotation-persistence.js.map
|