@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.
- package/README.md +52 -20
- package/README.zh-CN.md +52 -20
- package/lib/client/apply.js +48 -32
- package/lib/client/contracts.d.ts +3 -58
- package/lib/client/dsh/ReadOnlyConversation.d.ts +10 -0
- package/lib/client/dsh/ReadOnlyConversation.js +99 -0
- package/lib/client/dsh/SideChatOverlay.d.ts +7 -0
- package/lib/client/{rc6/Rc6SideChatOverlay.js → dsh/SideChatOverlay.js} +26 -56
- package/lib/client/{rc6 → dsh}/context.d.ts +4 -6
- package/lib/client/dsh/conversation-store.d.ts +22 -0
- package/lib/client/dsh/conversation-store.js +19 -0
- package/lib/client/dsh/primitive-labels.d.ts +2 -0
- package/lib/client/dsh/primitive-labels.js +4 -0
- package/lib/client/{rc6 → dsh}/remote-adapter.d.ts +2 -2
- package/lib/client/dsh/remote-adapter.js +22 -0
- package/lib/client/dsh/sessions-adapter.d.ts +56 -0
- package/lib/client/dsh/sessions-adapter.js +185 -0
- package/lib/client/index.d.ts +2 -2
- package/lib/client/panel/SelectionQuote.js +8 -1
- package/lib/client/panel/SideChatErrorState.js +1 -1
- package/lib/client/panel/SideChatModelSelect.d.ts +2 -1
- package/lib/client/panel/SideChatModelSelect.js +43 -24
- package/lib/client/panel/SideChatPanel.js +12 -5
- package/lib/client/panel/messages.d.ts +1 -1
- package/lib/client/panel/messages.js +8 -8
- package/lib/client/parent-composer/ParentConversationAnnotations.d.ts +3 -2
- package/lib/client/parent-composer/ParentConversationAnnotations.js +50 -30
- package/lib/client/parent-composer/add-to-conversation.d.ts +14 -28
- package/lib/client/parent-composer/add-to-conversation.js +131 -545
- package/lib/client/parent-composer/annotation-persistence.d.ts +8 -8
- package/lib/client/parent-composer/annotation-persistence.js +106 -102
- package/lib/client/parent-composer/composer-reference.d.ts +17 -55
- package/lib/client/parent-composer/composer-reference.js +20 -72
- package/lib/client/parent-composer/referenced-conversation.d.ts +5 -7
- package/lib/client/parent-composer/referenced-conversation.js +21 -73
- package/lib/client/selection/ConversationAnnotationMarkers.js +5 -1
- package/lib/client/selection/SelectionActions.js +2 -1
- package/lib/client/selection/conversation-dom.d.ts +5 -0
- package/lib/client/selection/conversation-dom.js +24 -0
- package/lib/client/selection/selection-controller.d.ts +1 -1
- package/lib/client/selection/selection-controller.js +7 -3
- package/lib/client/selection/selection-normalizer.js +1 -1
- package/lib/client/side-chat-controller.d.ts +28 -41
- package/lib/client/side-chat-controller.js +247 -337
- package/lib/client.js +84 -54
- package/lib/host/index.d.ts +1 -1
- package/lib/host/index.js +1 -1
- package/lib/host/read-only-chat-service.d.ts +30 -0
- package/lib/host/read-only-chat-service.js +315 -0
- package/lib/index.d.ts +16 -8
- package/lib/index.js +34 -13
- package/lib/remote.d.ts +23 -14
- package/lib/remote.js +2 -5
- package/lib/shared/constants.d.ts +0 -13
- package/lib/shared/constants.js +0 -13
- package/lib/shared/contracts.d.ts +52 -19
- package/lib/shared/contracts.js +2 -3
- package/lib/shared/error-codes.d.ts +1 -1
- package/lib/shared/error-codes.js +2 -2
- package/lib/shared/{archived-wire.d.ts → side-chat-wire.d.ts} +109 -20
- package/lib/shared/side-chat-wire.js +34 -0
- package/lib/typert.d.ts +3 -68
- package/lib/typert.js +60 -4
- package/package.json +76 -56
- package/lib/client/rc6/ArchivedConversation.d.ts +0 -14
- package/lib/client/rc6/ArchivedConversation.js +0 -231
- package/lib/client/rc6/Rc6SideChatOverlay.d.ts +0 -7
- package/lib/client/rc6/SideChatTool.d.ts +0 -22
- package/lib/client/rc6/SideChatTool.js +0 -404
- package/lib/client/rc6/primitive-labels.d.ts +0 -7
- package/lib/client/rc6/primitive-labels.js +0 -60
- package/lib/client/rc6/remote-adapter.js +0 -34
- package/lib/client/rc6/runtime-compat.d.ts +0 -120
- package/lib/client/rc6/runtime-compat.js +0 -187
- package/lib/client/rc6/sessions-adapter.d.ts +0 -64
- package/lib/client/rc6/sessions-adapter.js +0 -387
- package/lib/host/archived-fork-service.d.ts +0 -28
- package/lib/host/archived-fork-service.js +0 -309
- package/lib/shared/archived-wire.js +0 -47
- /package/lib/client/{rc6 → dsh}/context.js +0 -0
|
@@ -7,8 +7,9 @@ import { SideChatPanel } from '../panel/SideChatPanel.js';
|
|
|
7
7
|
import { ConversationAnnotationMarkers } from '../selection/ConversationAnnotationMarkers.js';
|
|
8
8
|
import { captureDomConversationSelection } from '../selection/selection-controller.js';
|
|
9
9
|
import { SelectionActions } from '../selection/SelectionActions.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { conversationChatRoot, focusConversationComposer } from '../selection/conversation-dom.js';
|
|
11
|
+
import { ReadOnlyConversation } from './ReadOnlyConversation.js';
|
|
12
|
+
import { DshSideChatSessions, selectionDescriptor } from './sessions-adapter.js';
|
|
12
13
|
const MORE_DETAILS_PROMPT = 'Please explain the selected passage in more detail.';
|
|
13
14
|
const TOUCH_SELECTION_SETTLE_MS = 300;
|
|
14
15
|
const TOUCH_ACTIVATION_SUPPRESS_MS = 750;
|
|
@@ -24,28 +25,8 @@ function captureEvent(event) {
|
|
|
24
25
|
'.dsh-side-chat-annotation-marker',
|
|
25
26
|
].join(', ')) !== null);
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
'[data-composer-seat] textarea',
|
|
30
|
-
'[data-composer-seat] [role="textbox"]',
|
|
31
|
-
'[data-composer-seat] [contenteditable="true"]',
|
|
32
|
-
].join(', '));
|
|
33
|
-
if (input === null)
|
|
34
|
-
return;
|
|
35
|
-
input.focus();
|
|
36
|
-
if (input instanceof HTMLTextAreaElement) {
|
|
37
|
-
input.setSelectionRange(input.value.length, input.value.length);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
const range = document.createRange();
|
|
41
|
-
range.selectNodeContents(input);
|
|
42
|
-
range.collapse(false);
|
|
43
|
-
const selection = window.getSelection();
|
|
44
|
-
selection?.removeAllRanges();
|
|
45
|
-
selection?.addRange(range);
|
|
46
|
-
}
|
|
47
|
-
/** rc.6 compatibility surface: selection toolbar plus a non-current child conversation panel. */
|
|
48
|
-
export function Rc6SideChatOverlay({ controller, sessions, }) {
|
|
28
|
+
/** Selection toolbar and plugin-owned read-only discussion panel. */
|
|
29
|
+
export function SideChatOverlay({ controller, sessions, }) {
|
|
49
30
|
const state = useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
|
|
50
31
|
const currentSessionId = useSyncExternalStore(sessions.subscribeList, () => sessions.currentSessionId(), () => sessions.currentSessionId());
|
|
51
32
|
const composerInput = useSyncExternalStore(sessions.subscribeConversationInput, sessions.currentConversationInputSnapshot, sessions.currentConversationInputSnapshot);
|
|
@@ -62,11 +43,11 @@ export function Rc6SideChatOverlay({ controller, sessions, }) {
|
|
|
62
43
|
const generation = ++captureGeneration.current;
|
|
63
44
|
const parentSessionId = sessions.currentSessionId();
|
|
64
45
|
const face = parentSessionId === undefined ? undefined : sessions.face(parentSessionId);
|
|
65
|
-
const conversationRoot =
|
|
46
|
+
const conversationRoot = parentSessionId === undefined ? undefined : conversationChatRoot(parentSessionId);
|
|
66
47
|
const browserSelection = window.getSelection();
|
|
67
48
|
if (parentSessionId === undefined
|
|
68
49
|
|| face === undefined
|
|
69
|
-
|| conversationRoot ===
|
|
50
|
+
|| conversationRoot === undefined
|
|
70
51
|
|| browserSelection === null
|
|
71
52
|
|| browserSelection.isCollapsed) {
|
|
72
53
|
if (generation === captureGeneration.current)
|
|
@@ -86,7 +67,7 @@ export function Rc6SideChatOverlay({ controller, sessions, }) {
|
|
|
86
67
|
},
|
|
87
68
|
},
|
|
88
69
|
});
|
|
89
|
-
if (generation === captureGeneration.current) {
|
|
70
|
+
if (generation === captureGeneration.current && sessions.currentSessionId() === parentSessionId) {
|
|
90
71
|
setActiveSelection({ value: captured, touch });
|
|
91
72
|
}
|
|
92
73
|
}
|
|
@@ -193,11 +174,13 @@ export function Rc6SideChatOverlay({ controller, sessions, }) {
|
|
|
193
174
|
touchInteraction.current = false;
|
|
194
175
|
cancelScheduledCapture();
|
|
195
176
|
if (event.key === 'Escape') {
|
|
177
|
+
// Escape in a menu, annotation editor, or main composer belongs to
|
|
178
|
+
// that control. Closing discards the temporary discussion, so it must
|
|
179
|
+
// not fire from every keyup in the document.
|
|
196
180
|
++captureGeneration.current;
|
|
197
181
|
annotationEditing.current = false;
|
|
198
182
|
setActiveSelection(null);
|
|
199
183
|
setEditingAnnotation(null);
|
|
200
|
-
void controller.close();
|
|
201
184
|
return;
|
|
202
185
|
}
|
|
203
186
|
if (captureEvent(event))
|
|
@@ -253,34 +236,25 @@ export function Rc6SideChatOverlay({ controller, sessions, }) {
|
|
|
253
236
|
const askDisabledReason = state.phase === 'closed'
|
|
254
237
|
? undefined
|
|
255
238
|
: 'Close the current Side Chat before starting another one.';
|
|
256
|
-
const childFace = state.childSessionId === undefined ? undefined : sessions.face(state.childSessionId);
|
|
257
|
-
const childCwd = state.childSessionId === undefined ? undefined : sessions.cwd(state.childSessionId);
|
|
258
|
-
const inheritedThroughSeq = state.inheritedThroughSeq;
|
|
259
239
|
const locale = navigator.language.toLowerCase().startsWith('zh') ? 'zh-CN' : 'en';
|
|
260
240
|
const modelDirectory = state.parentSessionId === undefined
|
|
261
241
|
? undefined
|
|
262
242
|
: sessions.modelDirectory?.(state.parentSessionId);
|
|
263
|
-
const addToConversationDisabled =
|
|
264
|
-
||
|
|
265
|
-
|| state.
|
|
266
|
-
|| state.childSessionId === undefined
|
|
267
|
-
|| state.phase !== 'ready';
|
|
243
|
+
const addToConversationDisabled = state.parentSessionId === undefined
|
|
244
|
+
|| state.chatId === undefined || state.phase !== 'ready'
|
|
245
|
+
|| !state.messages.some(message => message.role === 'assistant' && message.text.trim().length > 0);
|
|
268
246
|
const addToConversation = () => {
|
|
269
247
|
const parentSessionId = state.parentSessionId;
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
if (parentSessionId === undefined
|
|
273
|
-
|| childSessionId === undefined
|
|
274
|
-
|| inheritedThroughSeq === undefined
|
|
275
|
-
|| face === undefined) {
|
|
248
|
+
const chatId = state.chatId;
|
|
249
|
+
if (parentSessionId === undefined || chatId === undefined || state.phase !== 'ready') {
|
|
276
250
|
sessions.notify({ kind: 'warning', text: 'The Side Chat conversation is not ready to add yet.' });
|
|
277
251
|
return;
|
|
278
252
|
}
|
|
253
|
+
const parentTitle = sessions.title(parentSessionId);
|
|
279
254
|
const reference = referencedSideChatConversation({
|
|
280
|
-
conversationId:
|
|
281
|
-
title:
|
|
282
|
-
|
|
283
|
-
inheritedThroughSeq,
|
|
255
|
+
conversationId: chatId,
|
|
256
|
+
title: [locale === 'zh-CN' ? '侧边对话' : 'Side Chat', parentTitle].filter(Boolean).join(' · '),
|
|
257
|
+
messages: state.messages,
|
|
284
258
|
});
|
|
285
259
|
if (reference.conversation.length === 0) {
|
|
286
260
|
sessions.notify({ kind: 'warning', text: 'The Side Chat does not have any conversation history to add yet.' });
|
|
@@ -291,7 +265,7 @@ export function Rc6SideChatOverlay({ controller, sessions, }) {
|
|
|
291
265
|
sessions.notify({ kind: 'warning', text: 'Could not add the Side Chat to the main conversation.' });
|
|
292
266
|
return;
|
|
293
267
|
}
|
|
294
|
-
void sessions.openSession(parentSessionId).then(
|
|
268
|
+
void sessions.openSession(parentSessionId).then(() => focusConversationComposer(parentSessionId), () => {
|
|
295
269
|
sessions.notify({ kind: 'warning', text: 'The Side Chat was added, but its parent conversation could not be opened.' });
|
|
296
270
|
});
|
|
297
271
|
}
|
|
@@ -301,12 +275,12 @@ export function Rc6SideChatOverlay({ controller, sessions, }) {
|
|
|
301
275
|
};
|
|
302
276
|
const modelControl = modelDirectory === undefined
|
|
303
277
|
? undefined
|
|
304
|
-
: (_jsx(SideChatModelSelect, { directory: modelDirectory, selection: state.modelSelection, locked: ['creating', '
|
|
278
|
+
: (_jsx(SideChatModelSelect, { directory: modelDirectory, selection: state.modelSelection, locked: ['creating', 'closing'].includes(state.phase) || state.error?.operation === 'close', running: state.phase === 'running', validateInitialSelection: state.chatId === undefined, locale: locale, onInitialize: (selection, options) => {
|
|
305
279
|
if (options.remember)
|
|
306
280
|
void controller.selectModel(selection);
|
|
307
281
|
else
|
|
308
282
|
controller.initializeModel(selection);
|
|
309
|
-
}, onSelect: (selection) => controller.selectModel(selection) }, `${state.parentSessionId}:${state.
|
|
283
|
+
}, onSelect: (selection) => controller.selectModel(selection) }, `${state.parentSessionId}:${state.chatId ?? 'draft'}`));
|
|
310
284
|
return (_jsxs("div", { className: "dsh-side-chat-overlay", children: [_jsx(ConversationAnnotationMarkers, { annotations: annotations, ...editingAnnotation === null
|
|
311
285
|
? {}
|
|
312
286
|
: { activeAnnotationIndex: editingAnnotation.annotationIndex }, onEdit: (annotation, restoredSelection) => {
|
|
@@ -318,7 +292,7 @@ export function Rc6SideChatOverlay({ controller, sessions, }) {
|
|
|
318
292
|
} }), activeSelection !== null && (_jsx(SelectionActions, { selection: activeSelection.value, touchInteraction: activeSelection.touch, annotationNumber: sessions.nextConversationAnnotationNumber(), ...askDisabledReason === undefined ? {} : { askDisabledReason }, onAddToChat: (captured, comment) => {
|
|
319
293
|
try {
|
|
320
294
|
if (sessions.addSelectionToConversation(captured, comment))
|
|
321
|
-
|
|
295
|
+
focusConversationComposer(captured.parentSessionId);
|
|
322
296
|
else
|
|
323
297
|
sessions.notify({ kind: 'warning', text: 'Could not add the selection to the current chat.' });
|
|
324
298
|
}
|
|
@@ -373,12 +347,8 @@ export function Rc6SideChatOverlay({ controller, sessions, }) {
|
|
|
373
347
|
}, onMoreDetails: () => { }, onAskInSideChat: () => { }, onDismiss: () => {
|
|
374
348
|
annotationEditing.current = false;
|
|
375
349
|
setEditingAnnotation(null);
|
|
376
|
-
} }, `annotation:${String(editingAnnotation.annotationIndex)}`)), state.phase !== 'closed' && (_jsx(SideChatPanel, { state: state, locale: locale,
|
|
377
|
-
? {}
|
|
378
|
-
: {
|
|
379
|
-
embeddedConversation: (_jsx(ArchivedConversation, { face: childFace, inheritedThroughSeq: inheritedThroughSeq, controller: controller, cwd: childCwd, ...state.selection === undefined ? {} : { selection: state.selection }, locale: locale, modelControl: modelControl })),
|
|
380
|
-
}, modelControl: modelControl, onDraftChange: (draft) => { controller.setDraft(draft); }, onFirstSend: (question) => controller.sendFirst(question), onClose: () => controller.close(), onRetry: () => controller.retry(), onFocusParent: () => {
|
|
350
|
+
} }, `annotation:${String(editingAnnotation.annotationIndex)}`)), state.phase !== 'closed' && (_jsx(SideChatPanel, { state: state, locale: locale, embeddedConversation: _jsx(ReadOnlyConversation, { state: state, controller: controller, locale: locale, modelControl: modelControl }, state.chatId), modelControl: modelControl, onDraftChange: (draft) => { controller.setDraft(draft); }, onFirstSend: (question) => controller.sendFirst(question), onClose: () => controller.close(), onRetry: () => controller.retry(), onFocusParent: () => {
|
|
381
351
|
if (state.parentSessionId !== undefined)
|
|
382
352
|
void sessions.openSession(state.parentSessionId);
|
|
383
|
-
}, onAddToConversation: addToConversation, addToConversationDisabled: addToConversationDisabled, onRemoveSelection: () => { controller.clearSelection(); } }))] }));
|
|
353
|
+
}, onAddToConversation: addToConversation, addToConversationDisabled: addToConversationDisabled, ...state.chatId === undefined ? { onRemoveSelection: () => { controller.clearSelection(); } } : {} }))] }));
|
|
384
354
|
}
|
|
@@ -4,10 +4,10 @@ import type { ISessions } from '@deepseek-ai/dsh-api-session-controller/client';
|
|
|
4
4
|
import type { UiConversation } from '@deepseek-ai/dsh-client-ui-conversation/client';
|
|
5
5
|
import type { ModelDirectoryResolver } from '@deepseek-ai/dsh-client-ui-model-selection/client';
|
|
6
6
|
import type { SlotRegistry } from '@deepseek-ai/dsh-client-ui-renderer/client';
|
|
7
|
-
import type {
|
|
7
|
+
import type { UiWorkspace } from '@deepseek-ai/dsh-client-ui-workspace/client';
|
|
8
8
|
import type { ParentConversationService, SelectionReferenceSource } from '../parent-composer/add-to-conversation.js';
|
|
9
9
|
/** Browser-only Cordis view for a package that also compiles the Host half. */
|
|
10
|
-
export type
|
|
10
|
+
export type DshClientContext = Omit<Context, 'conversation' | 'inputTriggers' | 'modelDirectories' | 'remote' | 'sessions' | 'slots' | 'uiConversation' | 'uiWorkspace'> & {
|
|
11
11
|
readonly conversation: ParentConversationService;
|
|
12
12
|
readonly inputTriggers: {
|
|
13
13
|
registerSource(source: SelectionReferenceSource): () => void;
|
|
@@ -16,8 +16,6 @@ export type Rc6ClientContext = Omit<Context, 'conversation' | 'inputTriggers' |
|
|
|
16
16
|
readonly remote: ClientRemote;
|
|
17
17
|
readonly sessions: ISessions;
|
|
18
18
|
readonly slots: SlotRegistry;
|
|
19
|
-
|
|
20
|
-
readonly uiConversation
|
|
21
|
-
/** Added by DSH 0.1.2; absent on the monolithic 0.1.0/0.1.1 runtime. */
|
|
22
|
-
readonly uiSession?: Pick<UiSession, 'pendingInteractions'>;
|
|
19
|
+
readonly uiWorkspace: UiWorkspace;
|
|
20
|
+
readonly uiConversation: Pick<UiConversation, 'binding'>;
|
|
23
21
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ChatSnapshot } from '@deepseek-ai/dsh-client-ui-chat/client';
|
|
2
|
+
export type SideChatNodeStore = ChatSnapshot['nodes'];
|
|
3
|
+
export interface ParentConversationSnapshot {
|
|
4
|
+
readonly turnEnds: ReadonlyMap<number, number>;
|
|
5
|
+
readonly chatNodes: Pick<SideChatNodeStore, 'get'>;
|
|
6
|
+
}
|
|
7
|
+
export interface ParentConversationFace {
|
|
8
|
+
getSnapshot(): ParentConversationSnapshot;
|
|
9
|
+
subscribe(listener: () => void): () => void;
|
|
10
|
+
}
|
|
11
|
+
/** Read-only parent Chat projection, used only for selection anchors and turn boundaries. */
|
|
12
|
+
export declare class DshParentConversation implements ParentConversationFace {
|
|
13
|
+
private readonly chat;
|
|
14
|
+
private source;
|
|
15
|
+
private snapshot;
|
|
16
|
+
constructor(chat: {
|
|
17
|
+
getSnapshot(): ChatSnapshot | undefined;
|
|
18
|
+
subscribe(listener: () => void): () => void;
|
|
19
|
+
});
|
|
20
|
+
subscribe: (listener: () => void) => (() => void);
|
|
21
|
+
getSnapshot: () => ParentConversationSnapshot;
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const EMPTY = { turnEnds: new Map(), chatNodes: { get: () => undefined } };
|
|
2
|
+
/** Read-only parent Chat projection, used only for selection anchors and turn boundaries. */
|
|
3
|
+
export class DshParentConversation {
|
|
4
|
+
chat;
|
|
5
|
+
source;
|
|
6
|
+
snapshot = EMPTY;
|
|
7
|
+
constructor(chat) {
|
|
8
|
+
this.chat = chat;
|
|
9
|
+
}
|
|
10
|
+
subscribe = (listener) => this.chat.subscribe(listener);
|
|
11
|
+
getSnapshot = () => {
|
|
12
|
+
const source = this.chat.getSnapshot();
|
|
13
|
+
if (source !== this.source) {
|
|
14
|
+
this.source = source;
|
|
15
|
+
this.snapshot = source === undefined ? EMPTY : { turnEnds: source.legacy.turnEnds, chatNodes: source.nodes };
|
|
16
|
+
}
|
|
17
|
+
return this.snapshot;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SideChatRemote } from '../../shared/contracts.js';
|
|
2
|
-
import type {
|
|
3
|
-
export declare function
|
|
2
|
+
import type { DshClientContext } from './context.js';
|
|
3
|
+
export declare function mountSideChatRemote(ctx: DshClientContext): Promise<{
|
|
4
4
|
readonly remote: SideChatRemote;
|
|
5
5
|
readonly dispose: () => Promise<void>;
|
|
6
6
|
}>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import TYPERT_REMOTE from '../../remote.js';
|
|
2
|
+
function remoteFailure(error) {
|
|
3
|
+
const code = error.code;
|
|
4
|
+
const badRequest = code === 'bad-request' || code === 'gateway/bad-request';
|
|
5
|
+
return { code: badRequest ? 'invalid_request' : 'transport_error',
|
|
6
|
+
message: error.message || 'The Side Chat RPC failed.', recoverable: !badRequest };
|
|
7
|
+
}
|
|
8
|
+
async function unwrap(result) {
|
|
9
|
+
const value = await result;
|
|
10
|
+
return value.ok ? value.value : { ok: false, error: remoteFailure(value.error) };
|
|
11
|
+
}
|
|
12
|
+
export async function mountSideChatRemote(ctx) {
|
|
13
|
+
const dispose = await ctx.remote.$mount(TYPERT_REMOTE);
|
|
14
|
+
const rpc = ctx.get('remote.sideChat');
|
|
15
|
+
return { remote: {
|
|
16
|
+
create: request => unwrap(rpc.create(request)),
|
|
17
|
+
selectModel: request => unwrap(rpc.selectModel(request)),
|
|
18
|
+
stream: request => rpc.stream(request),
|
|
19
|
+
cancel: request => unwrap(rpc.cancel(request)),
|
|
20
|
+
close: request => unwrap(rpc.close(request)),
|
|
21
|
+
}, dispose };
|
|
22
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { ModelDirectory } from '@deepseek-ai/dsh-client-ui-model-selection/client';
|
|
2
|
+
import type { SideChatClientSessions } from '../contracts.js';
|
|
3
|
+
import { SessionId, type ConversationSelection, type SideChatModelSelection } from '../../shared/contracts.js';
|
|
4
|
+
import { type ParentComposerInputSnapshot } from '../parent-composer/add-to-conversation.js';
|
|
5
|
+
import { type ReferencedSideChatConversation } from '../parent-composer/referenced-conversation.js';
|
|
6
|
+
import type { DshClientContext } from './context.js';
|
|
7
|
+
import { type ParentConversationFace, type ParentConversationSnapshot } from './conversation-store.js';
|
|
8
|
+
declare module '@deepseek-ai/dsh-api-session-controller/client' {
|
|
9
|
+
interface SessionReferenceSourceMap {
|
|
10
|
+
sideChat: unknown;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** The only Session touched by the plugin is the parent: read its Chat, retain its editor, and insert explicit references. */
|
|
14
|
+
export declare class DshSideChatSessions implements SideChatClientSessions {
|
|
15
|
+
private readonly ctx;
|
|
16
|
+
private readonly faces;
|
|
17
|
+
private readonly parentInputs;
|
|
18
|
+
private readonly annotationPersistence;
|
|
19
|
+
private readonly modelPreferences;
|
|
20
|
+
constructor(ctx: DshClientContext);
|
|
21
|
+
readonly subscribeList: (listener: () => void) => (() => void);
|
|
22
|
+
readonly subscribeConversationInput: (listener: () => void) => (() => void);
|
|
23
|
+
readonly currentConversationInputSnapshot: () => ParentComposerInputSnapshot | undefined;
|
|
24
|
+
currentSessionId(): SessionId | undefined;
|
|
25
|
+
lastCompletedSeq(parentSessionId: SessionId): number | undefined;
|
|
26
|
+
selectionIsCurrent(selection: ConversationSelection): boolean;
|
|
27
|
+
addSelectionToConversation(selection: ConversationSelection, comment?: string): boolean;
|
|
28
|
+
addSideChatToConversation(parentSessionId: SessionId, reference: ReferencedSideChatConversation): boolean;
|
|
29
|
+
removeConversationAnnotation(index: number): boolean;
|
|
30
|
+
updateConversationAnnotation(index: number, comment?: string): boolean;
|
|
31
|
+
reconcileConversationAnnotationPersistence(): void;
|
|
32
|
+
nextConversationAnnotationNumber(): number;
|
|
33
|
+
removeConversationAnnotations(id?: SessionId | undefined): boolean;
|
|
34
|
+
retainParent(id: SessionId): () => void;
|
|
35
|
+
openSession(id: SessionId): Promise<void>;
|
|
36
|
+
notify(message: {
|
|
37
|
+
readonly kind: 'status' | 'warning';
|
|
38
|
+
readonly text: string;
|
|
39
|
+
}): void;
|
|
40
|
+
face(id: SessionId): ParentConversationFace | undefined;
|
|
41
|
+
title(id: SessionId): string | undefined;
|
|
42
|
+
modelDirectory(id: SessionId): ModelDirectory | undefined;
|
|
43
|
+
sideChatModelPreference(): SideChatModelSelection | undefined;
|
|
44
|
+
rememberSideChatModelPreference(model: SideChatModelSelection): void;
|
|
45
|
+
private currentParentInput;
|
|
46
|
+
private parentInput;
|
|
47
|
+
}
|
|
48
|
+
export declare function selectionDescriptor(snapshot: ParentConversationSnapshot, anchorKey: string): {
|
|
49
|
+
readonly nodeKey: string;
|
|
50
|
+
readonly nodeKind: string;
|
|
51
|
+
readonly turnKey: string;
|
|
52
|
+
readonly seq: number;
|
|
53
|
+
readonly source: 'user' | 'assistant' | 'context' | 'code';
|
|
54
|
+
readonly modelVisible: boolean;
|
|
55
|
+
readonly settled: boolean;
|
|
56
|
+
} | undefined;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { SessionId } from '../../shared/contracts.js';
|
|
2
|
+
import { addSelectionToConversation as addSelection, conversationAnnotations, removeConversationAnnotation as removeAnnotation, removeConversationAnnotations as removeAnnotations, updateConversationAnnotation as updateAnnotation, } from '../parent-composer/add-to-conversation.js';
|
|
3
|
+
import { ConversationAnnotationPersistence } from '../parent-composer/annotation-persistence.js';
|
|
4
|
+
import { addReferencedSideChatToConversation } from '../parent-composer/referenced-conversation.js';
|
|
5
|
+
import { SideChatModelPreferences } from '../model-preference.js';
|
|
6
|
+
import { DshParentConversation } from './conversation-store.js';
|
|
7
|
+
function dshId(id) { return id; }
|
|
8
|
+
function locationSettled(location) {
|
|
9
|
+
if (location.kind === 'turn')
|
|
10
|
+
return location.turn.status === 'closed';
|
|
11
|
+
if (location.kind === 'step')
|
|
12
|
+
return location.turn.status === 'closed' && location.step.status === 'closed';
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
/** The only Session touched by the plugin is the parent: read its Chat, retain its editor, and insert explicit references. */
|
|
16
|
+
export class DshSideChatSessions {
|
|
17
|
+
ctx;
|
|
18
|
+
faces = new WeakMap();
|
|
19
|
+
parentInputs = new WeakMap();
|
|
20
|
+
annotationPersistence = new ConversationAnnotationPersistence();
|
|
21
|
+
modelPreferences = new SideChatModelPreferences();
|
|
22
|
+
constructor(ctx) {
|
|
23
|
+
this.ctx = ctx;
|
|
24
|
+
}
|
|
25
|
+
subscribeList = (listener) => this.ctx.sessions.list.subscribe(listener);
|
|
26
|
+
subscribeConversationInput = (listener) => {
|
|
27
|
+
let input;
|
|
28
|
+
let removeInput = () => { };
|
|
29
|
+
const bind = () => {
|
|
30
|
+
const next = this.currentParentInput();
|
|
31
|
+
if (next === input)
|
|
32
|
+
return;
|
|
33
|
+
removeInput();
|
|
34
|
+
input = next;
|
|
35
|
+
removeInput = input?.state.subscribe(listener) ?? (() => { });
|
|
36
|
+
};
|
|
37
|
+
bind();
|
|
38
|
+
const removeList = this.ctx.sessions.list.subscribe(() => { bind(); listener(); });
|
|
39
|
+
return () => { removeList(); removeInput(); };
|
|
40
|
+
};
|
|
41
|
+
currentConversationInputSnapshot = () => this.currentParentInput()?.state.getSnapshot();
|
|
42
|
+
currentSessionId() {
|
|
43
|
+
const current = Object.entries(this.ctx.sessions.list.getSnapshot().byId)
|
|
44
|
+
.find(([, summary]) => (summary.retainedBy.mainView ?? 0) > 0)?.[0];
|
|
45
|
+
return current === undefined ? undefined : SessionId(current);
|
|
46
|
+
}
|
|
47
|
+
lastCompletedSeq(parentSessionId) {
|
|
48
|
+
const snapshot = this.face(parentSessionId)?.getSnapshot();
|
|
49
|
+
let latest;
|
|
50
|
+
for (const seq of snapshot?.turnEnds.values() ?? [])
|
|
51
|
+
if (latest === undefined || seq > latest)
|
|
52
|
+
latest = seq;
|
|
53
|
+
return latest;
|
|
54
|
+
}
|
|
55
|
+
selectionIsCurrent(selection) {
|
|
56
|
+
const snapshot = this.face(selection.parentSessionId)?.getSnapshot();
|
|
57
|
+
if (snapshot === undefined)
|
|
58
|
+
return false;
|
|
59
|
+
return selection.fragments.every(fragment => {
|
|
60
|
+
const node = snapshot.chatNodes.get(fragment.nodeKey);
|
|
61
|
+
return node !== undefined && node.visibility === 'visible' && node.kind === fragment.nodeKind
|
|
62
|
+
&& node.anchorSeq === fragment.seq && locationSettled(node.location);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
addSelectionToConversation(selection, comment) {
|
|
66
|
+
if (this.currentSessionId() !== selection.parentSessionId)
|
|
67
|
+
return false;
|
|
68
|
+
const input = this.currentParentInput();
|
|
69
|
+
if (input === undefined || !addSelection(input, selection, comment))
|
|
70
|
+
return false;
|
|
71
|
+
this.annotationPersistence.reconcile(selection.parentSessionId, input);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
addSideChatToConversation(parentSessionId, reference) {
|
|
75
|
+
const scope = this.ctx.sessions.scope(dshId(parentSessionId));
|
|
76
|
+
const input = scope === undefined ? undefined : this.parentInput(scope);
|
|
77
|
+
if (input === undefined || !addReferencedSideChatToConversation(input, reference))
|
|
78
|
+
return false;
|
|
79
|
+
this.annotationPersistence.reconcile(parentSessionId, input);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
removeConversationAnnotation(index) {
|
|
83
|
+
const id = this.currentSessionId();
|
|
84
|
+
const input = this.currentParentInput();
|
|
85
|
+
if (id === undefined || input === undefined || !removeAnnotation(input, index))
|
|
86
|
+
return false;
|
|
87
|
+
this.annotationPersistence.reconcile(id, input);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
updateConversationAnnotation(index, comment) {
|
|
91
|
+
const id = this.currentSessionId();
|
|
92
|
+
const input = this.currentParentInput();
|
|
93
|
+
if (id === undefined || input === undefined || !updateAnnotation(input, index, comment))
|
|
94
|
+
return false;
|
|
95
|
+
this.annotationPersistence.reconcile(id, input);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
reconcileConversationAnnotationPersistence() {
|
|
99
|
+
const id = this.currentSessionId();
|
|
100
|
+
const input = this.currentParentInput();
|
|
101
|
+
if (id !== undefined && input !== undefined)
|
|
102
|
+
this.annotationPersistence.reconcile(id, input);
|
|
103
|
+
}
|
|
104
|
+
nextConversationAnnotationNumber() {
|
|
105
|
+
const snapshot = this.currentConversationInputSnapshot();
|
|
106
|
+
return snapshot === undefined ? 1 : conversationAnnotations(snapshot).length + 1;
|
|
107
|
+
}
|
|
108
|
+
removeConversationAnnotations(id = this.currentSessionId()) {
|
|
109
|
+
const scope = id === undefined ? undefined : this.ctx.sessions.scope(dshId(id));
|
|
110
|
+
const input = scope === undefined ? undefined : this.parentInput(scope);
|
|
111
|
+
if (id === undefined || input === undefined || !removeAnnotations(input))
|
|
112
|
+
return false;
|
|
113
|
+
this.annotationPersistence.reconcile(id, input);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
retainParent(id) {
|
|
117
|
+
const reference = this.ctx.sessions.retain(dshId(id), { source: 'sideChat' });
|
|
118
|
+
let active = true;
|
|
119
|
+
void reference.ready.catch(() => { if (active)
|
|
120
|
+
this.notify({ kind: 'warning', text: 'The parent conversation could not be loaded.' }); });
|
|
121
|
+
return () => { active = false; reference.release(); };
|
|
122
|
+
}
|
|
123
|
+
async openSession(id) { this.ctx.uiWorkspace.openSession(dshId(id)); }
|
|
124
|
+
notify(message) {
|
|
125
|
+
console[message.kind === 'warning' ? 'warn' : 'info'](`[dsh-side-chat] ${message.text}`);
|
|
126
|
+
this.currentParentInput()?.notify?.(message.kind === 'warning' ? 'error' : 'info', message.text);
|
|
127
|
+
}
|
|
128
|
+
face(id) {
|
|
129
|
+
const source = this.ctx.sessions.binding(dshId(id))?.session;
|
|
130
|
+
if (source === undefined)
|
|
131
|
+
return;
|
|
132
|
+
const existing = this.faces.get(source);
|
|
133
|
+
if (existing !== undefined)
|
|
134
|
+
return existing;
|
|
135
|
+
const conversation = this.ctx.uiConversation.binding(source.sessionId);
|
|
136
|
+
conversation.activate('chat');
|
|
137
|
+
const face = new DshParentConversation(conversation.target('chat'));
|
|
138
|
+
this.faces.set(source, face);
|
|
139
|
+
return face;
|
|
140
|
+
}
|
|
141
|
+
title(id) { return this.ctx.sessions.list.getSnapshot().byId[dshId(id)]?.displayTitle; }
|
|
142
|
+
modelDirectory(id) {
|
|
143
|
+
try {
|
|
144
|
+
return this.ctx.modelDirectories.directoryFor(dshId(id));
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
sideChatModelPreference() { return this.modelPreferences.get(); }
|
|
151
|
+
rememberSideChatModelPreference(model) { this.modelPreferences.set(model); }
|
|
152
|
+
currentParentInput() {
|
|
153
|
+
const id = this.currentSessionId();
|
|
154
|
+
const scope = id === undefined ? undefined : this.ctx.sessions.scope(dshId(id));
|
|
155
|
+
return scope === undefined ? undefined : this.parentInput(scope);
|
|
156
|
+
}
|
|
157
|
+
parentInput(scope) {
|
|
158
|
+
const source = this.ctx.conversation.input.for(scope);
|
|
159
|
+
if (source === undefined)
|
|
160
|
+
return;
|
|
161
|
+
const existing = this.parentInputs.get(source);
|
|
162
|
+
if (existing !== undefined)
|
|
163
|
+
return existing;
|
|
164
|
+
const input = {
|
|
165
|
+
state: source.state, setDraft: text => { source.setDraft(text); },
|
|
166
|
+
notify: (level, text) => source.notify?.(level, text),
|
|
167
|
+
insertReference: (reference, span) => source.insertReference(reference, span),
|
|
168
|
+
replaceText: (text, span) => scope.bail(scope, 'slash/input-insert-text', { text, span }) === true,
|
|
169
|
+
};
|
|
170
|
+
this.parentInputs.set(source, input);
|
|
171
|
+
return input;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
export function selectionDescriptor(snapshot, anchorKey) {
|
|
175
|
+
const node = snapshot.chatNodes.get(anchorKey);
|
|
176
|
+
if (node === undefined || node.visibility !== 'visible')
|
|
177
|
+
return;
|
|
178
|
+
const source = node.kind === 'user' || node.kind === 'steering' ? 'user'
|
|
179
|
+
: node.kind === 'assistant-step' ? 'assistant' : node.kind === 'context' ? 'context' : undefined;
|
|
180
|
+
const turn = node.location.kind === 'turn' || node.location.kind === 'step' ? node.location.turn.turn : undefined;
|
|
181
|
+
if (source === undefined || turn === undefined)
|
|
182
|
+
return;
|
|
183
|
+
return { nodeKey: node.key, nodeKind: node.kind, turnKey: `turn:${String(turn)}`, seq: node.anchorSeq,
|
|
184
|
+
source, modelVisible: true, settled: locationSettled(node.location) };
|
|
185
|
+
}
|
package/lib/client/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { apply, inject, name } from './apply.js';
|
|
2
|
-
export type { SideChatActionResult, SideChatClientSessions,
|
|
2
|
+
export type { SideChatActionResult, SideChatClientSessions, } from './contracts.js';
|
|
3
3
|
export { SideChatController } from './side-chat-controller.js';
|
|
4
4
|
export { buildSideChatPrompt } from './parent-composer/add-to-conversation.js';
|
|
5
5
|
export { SideChatPanel, type SideChatPanelProps } from './panel/SideChatPanel.js';
|
|
@@ -7,4 +7,4 @@ export { SelectionActions, type SelectionActionsProps } from './selection/Select
|
|
|
7
7
|
export { captureDomConversationSelection, restoreDomConversationSelection, type RestoredConversationSelection, type SelectionAnchorResolver, type SelectionNodeDescriptor, } from './selection/selection-controller.js';
|
|
8
8
|
export { selectionFitsLimit, summarizeSelection, utf8ByteLength } from './selection/selection-limits.js';
|
|
9
9
|
export { assertSelectionCurrent, finalizeConversationSelection, normalizeSelectedText, SelectionValidationError, type ConversationSelectionInput, } from './selection/selection-normalizer.js';
|
|
10
|
-
export type { ConversationSelection, SelectionFragment, SelectionRect, SideChatModelSelection, SideChatState, } from '../shared/contracts.js';
|
|
10
|
+
export type { ConversationSelection, SelectionFragment, SelectionRect, SideChatModelSelection, SideChatMessage, SideChatId, SideChatState, } from '../shared/contracts.js';
|
|
@@ -60,7 +60,14 @@ export function SelectionQuote({ selections, messages, onRemove, }) {
|
|
|
60
60
|
return (_jsxs("section", { ref: quoteRef, className: "dsh-side-chat-quote", "aria-label": messages.selectedPassage, "data-expanded": expanded || undefined, "data-hovered": hovered || undefined, onMouseEnter: () => {
|
|
61
61
|
keepHoverOpen();
|
|
62
62
|
constrainDetailsToBoundary();
|
|
63
|
-
}, onMouseLeave: closeHoverAfterGrace, onFocusCapture: constrainDetailsToBoundary,
|
|
63
|
+
}, onMouseLeave: closeHoverAfterGrace, onFocusCapture: constrainDetailsToBoundary, onKeyDown: (event) => {
|
|
64
|
+
if (event.key !== 'Escape' || (!expanded && !hovered))
|
|
65
|
+
return;
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
event.stopPropagation();
|
|
68
|
+
setExpanded(false);
|
|
69
|
+
setHovered(false);
|
|
70
|
+
}, children: [_jsxs("div", { className: "dsh-side-chat-quote-chip", children: [_jsxs("button", { type: "button", className: "dsh-side-chat-quote-trigger", "aria-expanded": expanded, "aria-label": `${expanded ? messages.collapse : messages.expand}: ${messages.selectedPassage}`, onClick: () => {
|
|
64
71
|
if (expanded) {
|
|
65
72
|
if (leaveTimerRef.current !== undefined)
|
|
66
73
|
window.clearTimeout(leaveTimerRef.current);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/** Error strip with one direct retry action. */
|
|
3
3
|
export function SideChatErrorState({ error, messages, onRetry, }) {
|
|
4
|
-
return (_jsxs("section", { className: "dsh-side-chat-error", role: "alert", children: [_jsx("strong", { children: error.
|
|
4
|
+
return (_jsxs("section", { className: "dsh-side-chat-error", role: "alert", children: [_jsx("strong", { children: error.operation === 'close' ? messages.closeError : messages.genericError }), _jsx("p", { children: error.message }), error.recoverable && _jsx("button", { type: "button", onClick: onRetry, children: messages.retry })] }));
|
|
5
5
|
}
|
|
@@ -5,6 +5,7 @@ export interface SideChatModelSelectProps {
|
|
|
5
5
|
readonly directory: ModelDirectory;
|
|
6
6
|
readonly selection?: SideChatModelSelection | undefined;
|
|
7
7
|
readonly locked: boolean;
|
|
8
|
+
readonly running?: boolean;
|
|
8
9
|
readonly validateInitialSelection?: boolean;
|
|
9
10
|
readonly locale?: 'en' | 'zh-CN';
|
|
10
11
|
readonly onInitialize: (selection: SideChatModelSelection, options: {
|
|
@@ -13,4 +14,4 @@ export interface SideChatModelSelectProps {
|
|
|
13
14
|
readonly onSelect: (selection: SideChatModelSelection) => Promise<SideChatActionResult<SideChatModelSelection>>;
|
|
14
15
|
}
|
|
15
16
|
/** Side Chat projection of DSH Web's native composer model selector. */
|
|
16
|
-
export declare function SideChatModelSelect({ directory, selection, locked, validateInitialSelection, locale, onInitialize, onSelect, }: SideChatModelSelectProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export declare function SideChatModelSelect({ directory, selection, locked, running, validateInitialSelection, locale, onInitialize, onSelect, }: SideChatModelSelectProps): import("react/jsx-runtime").JSX.Element;
|