@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
|
@@ -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
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
216
|
-
|
|
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', '
|
|
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', '
|
|
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.
|
|
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
|
|
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: '
|
|
16
|
-
referenceOnly: '
|
|
17
|
-
cannotReopen: '
|
|
18
|
-
|
|
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
|
-
|
|
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 {
|
|
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:
|
|
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
|
|
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
|
-
|
|
30
|
-
const
|
|
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
|
-
}, [
|
|
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
|
-
|
|
72
|
-
|
|
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 =
|
|
101
|
+
const referenced = parseReferencedConversationPrompt(message);
|
|
80
102
|
if (referenced !== undefined) {
|
|
81
|
-
|
|
103
|
+
references.push(referenced.reference);
|
|
82
104
|
message = referenced.message;
|
|
83
105
|
continue;
|
|
84
106
|
}
|
|
85
107
|
break;
|
|
86
108
|
}
|
|
87
|
-
if (annotations === 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 =
|
|
102
|
-
const visible = parsed.reference
|
|
103
|
-
|
|
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
|
|
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
|
-
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
/**
|
|
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;
|