@xmanrui/dsh-im 4.19.0 → 4.19.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/lib/client.js +116 -10
- package/lib/index.js +210 -210
- package/package.json +1 -1
- package/plugin-src/client/bot-alias.js +78 -1
- package/plugin-src/client/styles.js +17 -8
- package/plugin-src/host/session-sync-coordinator.mjs +14 -13
- package/src/channels/feishu/bridge.mjs +219 -262
- package/src/channels/shared/session-sync-registry.mjs +16 -28
package/package.json
CHANGED
|
@@ -72,10 +72,87 @@ function AliasDialog({ bot, onSave, onClose }) {
|
|
|
72
72
|
return globalThis.document?.body ? createPortal(content, document.body) : content;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
function BotNameTooltip({ anchorRef, id, name, onDismiss }) {
|
|
76
|
+
const tooltipRef = React.useRef(null);
|
|
77
|
+
const [position, setPosition] = React.useState(null);
|
|
78
|
+
React.useLayoutEffect(() => {
|
|
79
|
+
const anchor = anchorRef.current;
|
|
80
|
+
const tooltip = tooltipRef.current;
|
|
81
|
+
if (!anchor || !tooltip) return undefined;
|
|
82
|
+
const document = anchor.ownerDocument;
|
|
83
|
+
const view = document.defaultView;
|
|
84
|
+
const place = () => {
|
|
85
|
+
const rect = anchor.getBoundingClientRect();
|
|
86
|
+
const { width, height } = tooltip.getBoundingClientRect();
|
|
87
|
+
const viewport = document.documentElement;
|
|
88
|
+
const margin = 8;
|
|
89
|
+
const below = rect.bottom + 6;
|
|
90
|
+
setPosition({
|
|
91
|
+
left: Math.max(margin, Math.min(rect.left, viewport.clientWidth - width - margin)),
|
|
92
|
+
top: Math.max(margin, Math.min(
|
|
93
|
+
below + height <= viewport.clientHeight - margin ? below : rect.top - height - 6,
|
|
94
|
+
viewport.clientHeight - height - margin,
|
|
95
|
+
)),
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
const dismiss = (event) => {
|
|
99
|
+
if (event.key === 'Escape') { event.stopPropagation(); onDismiss(); }
|
|
100
|
+
};
|
|
101
|
+
place();
|
|
102
|
+
view.addEventListener('resize', place);
|
|
103
|
+
// Capture nested scrolling containers as well as page scrolling.
|
|
104
|
+
document.addEventListener('scroll', onDismiss, true);
|
|
105
|
+
document.addEventListener('keydown', dismiss, true);
|
|
106
|
+
return () => {
|
|
107
|
+
view.removeEventListener('resize', place);
|
|
108
|
+
document.removeEventListener('scroll', onDismiss, true);
|
|
109
|
+
document.removeEventListener('keydown', dismiss, true);
|
|
110
|
+
};
|
|
111
|
+
}, [anchorRef, name, onDismiss]);
|
|
112
|
+
const body = anchorRef.current?.ownerDocument.body;
|
|
113
|
+
return body ? createPortal(h('span', {
|
|
114
|
+
ref: tooltipRef, id, role: 'tooltip', className: 'dim-botNameTooltip',
|
|
115
|
+
style: position ?? { visibility: 'hidden' },
|
|
116
|
+
}, name), body) : null;
|
|
117
|
+
}
|
|
118
|
+
|
|
75
119
|
export function BotName({ bot, id, disabled = false, onSave }) {
|
|
76
120
|
const [open, setOpen] = React.useState(false);
|
|
121
|
+
const nameRef = React.useRef(null);
|
|
122
|
+
const tooltipId = React.useId();
|
|
123
|
+
const [truncated, setTruncated] = React.useState(false);
|
|
124
|
+
const [hovered, setHovered] = React.useState(false);
|
|
125
|
+
const [focused, setFocused] = React.useState(false);
|
|
126
|
+
const [dismissed, setDismissed] = React.useState(false);
|
|
127
|
+
const dismissTooltip = React.useCallback(() => setDismissed(true), []);
|
|
128
|
+
const measure = React.useCallback(() => {
|
|
129
|
+
const node = nameRef.current;
|
|
130
|
+
setTruncated(Boolean(node && node.scrollWidth > node.clientWidth));
|
|
131
|
+
}, []);
|
|
132
|
+
React.useEffect(() => {
|
|
133
|
+
const node = nameRef.current;
|
|
134
|
+
if (!node) return undefined;
|
|
135
|
+
measure();
|
|
136
|
+
const view = node.ownerDocument.defaultView;
|
|
137
|
+
const observer = view.ResizeObserver ? new view.ResizeObserver(measure) : null;
|
|
138
|
+
observer?.observe(node);
|
|
139
|
+
view.addEventListener('resize', measure);
|
|
140
|
+
return () => { observer?.disconnect(); view.removeEventListener('resize', measure); };
|
|
141
|
+
}, [bot.name, measure]);
|
|
142
|
+
const showTooltip = truncated && !dismissed && !open && (hovered || focused);
|
|
77
143
|
return h('div', { className: 'dim-aliasName' },
|
|
78
|
-
h('h3', {
|
|
144
|
+
h('h3', {
|
|
145
|
+
id, ref: nameRef, tabIndex: truncated ? 0 : undefined,
|
|
146
|
+
'aria-describedby': showTooltip ? tooltipId : undefined,
|
|
147
|
+
onMouseEnter: () => { measure(); setHovered(true); setDismissed(false); },
|
|
148
|
+
onMouseLeave: () => setHovered(false),
|
|
149
|
+
onFocus: () => { measure(); setFocused(true); setDismissed(false); },
|
|
150
|
+
onBlur: () => setFocused(false),
|
|
151
|
+
onClick: dismissTooltip,
|
|
152
|
+
}, bot.name),
|
|
153
|
+
showTooltip ? h(BotNameTooltip, {
|
|
154
|
+
anchorRef: nameRef, id: tooltipId, name: bot.name, onDismiss: dismissTooltip,
|
|
155
|
+
}) : null,
|
|
79
156
|
h('span', {
|
|
80
157
|
className: 'dim-aliasEntry',
|
|
81
158
|
onClick: (event) => event.stopPropagation(),
|
|
@@ -3,9 +3,15 @@ export const IM_STYLE_ID = 'xmanrui-dsh-im-settings';
|
|
|
3
3
|
const CSS = String.raw`
|
|
4
4
|
.dim-aliasName { display: flex; align-items: center; gap: 4px; min-width: 0; }
|
|
5
5
|
.dim-aliasName h3 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
6
|
+
.dim-aliasName h3:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; border-radius: 3px; }
|
|
7
|
+
.dim-botNameTooltip { position: fixed; z-index: 1000; box-sizing: border-box; width: max-content; max-width: min(320px, calc(100vw - 16px)); padding: 6px 9px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 8px 24px rgb(31 35 41 / 14%); font: 500 12px/18px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; white-space: normal; overflow-wrap: anywhere; pointer-events: none; animation: dim-botNameTooltip-in .15s ease; }
|
|
8
|
+
@keyframes dim-botNameTooltip-in { from { opacity: 0; } to { opacity: 1; } }
|
|
9
|
+
@media (prefers-reduced-motion: reduce) { .dim-botNameTooltip { animation: none; } }
|
|
6
10
|
.dim-aliasEntry { display: inline-flex; flex: none; }
|
|
7
|
-
.dim-aliasEdit { display: grid; place-items: center; width: 28px; height: 28px; padding: 4px; border: 0; border-radius: 5px; color: var(--dsw-alias-label-
|
|
8
|
-
.dim-aliasEdit
|
|
11
|
+
.dim-aliasEdit { display: grid; place-items: center; width: 28px; height: 28px; padding: 4px; border: 0; border-radius: 5px; color: var(--dsw-alias-label-tertiary, #8f959e); background: transparent; cursor: pointer; }
|
|
12
|
+
.dim-aliasEdit svg { opacity: .55; transition: opacity .15s ease; }
|
|
13
|
+
.dim-aliasName:hover .dim-aliasEdit:not(:disabled) svg, .dim-aliasEdit:focus-visible svg { opacity: 1; }
|
|
14
|
+
.dim-aliasEdit:hover:not(:disabled), .dim-aliasEdit:focus-visible { color: var(--dsw-alias-state-business-primary, #3370ff); background: var(--dsw-alias-interactive-bg-hover, #f7f8fa); }
|
|
9
15
|
.dim-aliasDialog { box-sizing: border-box; width: min(380px, calc(100% - 32px)); max-height: calc(100dvh - 32px); overflow-y: auto; padding: 22px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 12px 36px rgb(0 0 0 / 18%); font: 13px/1.5 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
10
16
|
.dim-aliasDialog * { box-sizing: border-box; }
|
|
11
17
|
.dim-aliasDialog::backdrop { background: rgb(15 17 21 / 30%); }
|
|
@@ -401,9 +407,9 @@ const CSS = String.raw`
|
|
|
401
407
|
/* Header tooltips may extend beyond the card; the collapsible body clips its own content. */
|
|
402
408
|
.dim-panel .dim-botCard { position: relative; min-width: 0; width: 100%; max-width: 100%; overflow: visible; border: 1px solid var(--dsw-alias-border-l2, #e5e6eb); border-radius: 14px; background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 1px 2px rgb(31 35 41 / 3%); }
|
|
403
409
|
.dim-panel .dim-botCard::before { display: none; }
|
|
404
|
-
.dim-panel .dim-botCardBody { position: relative; min-width: 0; width: 100%; max-width: 100%; padding: 12px; }
|
|
410
|
+
.dim-panel .dim-botCardBody { position: relative; min-width: 0; width: 100%; max-width: 100%; padding: 12px 8px; }
|
|
405
411
|
.dim-collapsibleAccount { min-width: 0; display: flex; flex-direction: column; }
|
|
406
|
-
.dim-collapsibleHead { min-width: 0; display: flex; align-items: center; gap:
|
|
412
|
+
.dim-collapsibleHead { min-width: 0; display: flex; align-items: center; gap: 4px; cursor: pointer; user-select: none; -webkit-user-select: none; }
|
|
407
413
|
.dim-collapsibleHead:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; border-radius: 8px; }
|
|
408
414
|
.dim-collapsibleHeaderContent { min-width: 0; flex: 1 1 auto; display: flex; align-items: center; }
|
|
409
415
|
.dim-collapsibleChevron { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 9px; height: 9px; border-right: 1.6px solid var(--dsw-alias-label-tertiary, #8f959e); border-bottom: 1.6px solid var(--dsw-alias-label-tertiary, #8f959e); transform: rotate(-45deg); transition: transform .22s cubic-bezier(.4, 0, .2, 1); transform-origin: 50% 50%; }
|
|
@@ -413,14 +419,17 @@ const CSS = String.raw`
|
|
|
413
419
|
.dim-collapsibleAccount.is-open > .dim-collapsibleBody { grid-template-rows: 1fr; }
|
|
414
420
|
.dim-collapsibleBodyInner { min-height: 0; overflow: hidden; }
|
|
415
421
|
.dim-collapsibleAccount:not(.is-open) .dim-collapsibleBodyInner { visibility: hidden; }
|
|
416
|
-
|
|
417
|
-
|
|
422
|
+
/* Reclaim horizontal spacing for names while keeping status on the same row,
|
|
423
|
+
including when a channel's mobile stylesheet requests a column layout. */
|
|
424
|
+
.dim-panel .dim-botCardTop { min-width: 0; width: 100%; max-width: 100%; display: flex; flex-direction: row; flex-wrap: nowrap; align-items: flex-start; justify-content: space-between; gap: 6px; }
|
|
425
|
+
.dim-panel .dim-botIdentity { min-width: 0; flex: 1 1 0; display: flex; align-items: center; gap: 6px; }
|
|
418
426
|
.dim-panel .dim-botAvatar { flex: none; width: 38px; height: 38px; display: grid; place-items: center; overflow: hidden; border-radius: 11px; box-shadow: none; }
|
|
419
427
|
.dim-panel .dim-botAvatar svg { width: 27px; height: 27px; }
|
|
420
|
-
.dim-panel .dim-botName { min-width: 0; }
|
|
428
|
+
.dim-panel .dim-botName { min-width: 0; flex: 1; }
|
|
429
|
+
.dim-panel .dim-aliasName { gap: 2px; }
|
|
421
430
|
.dim-panel .dim-botName h3 { overflow: hidden; margin: 0; color: var(--dsw-alias-label-primary, #1f2329); font-size: 15px; font-weight: 650; line-height: normal; text-overflow: ellipsis; white-space: nowrap; }
|
|
422
431
|
.dim-panel .dim-botName p { overflow: hidden; margin: 4px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font: 12px ui-monospace, SFMono-Regular, monospace; line-height: normal; text-overflow: ellipsis; white-space: nowrap; }
|
|
423
|
-
.dim-panel .dim-botCardTools { flex: none; display: flex; align-items: flex-start; gap:
|
|
432
|
+
.dim-panel .dim-botCardTools { flex: none; display: flex; align-items: flex-start; gap: 4px; }
|
|
424
433
|
.dim-panel .dim-botHealthGroup { min-width: 0; max-width: 100%; flex: none; display: grid; justify-items: end; gap: 5px; }
|
|
425
434
|
.dim-panel .dim-botCard .dim-botHealth { flex: none; min-height: 0; display: inline-flex; align-items: center; gap: 7px; padding: 0; border: 0; border-radius: 0; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; font: inherit; font-size: 12px; font-weight: 400; line-height: normal; white-space: nowrap; }
|
|
426
435
|
.dim-panel .dim-lastChecked { display: inline-flex; align-items: baseline; gap: 4px; color: var(--dsw-alias-label-tertiary, #8f959e); font: inherit; font-size: 11px; font-weight: 400; line-height: normal; white-space: nowrap; }
|
|
@@ -3,10 +3,7 @@ import {
|
|
|
3
3
|
consumeDshImInputOrigin,
|
|
4
4
|
textFromHarnessContent,
|
|
5
5
|
} from '../../src/channels/shared/harness-client.mjs';
|
|
6
|
-
import {
|
|
7
|
-
isSessionSyncMirrored,
|
|
8
|
-
releaseSessionSyncMirror,
|
|
9
|
-
} from '../../src/channels/shared/session-sync-registry.mjs';
|
|
6
|
+
import { deliverSessionSyncMirror } from '../../src/channels/shared/session-sync-registry.mjs';
|
|
10
7
|
|
|
11
8
|
const DSH_USER_PREFIX = '[来自 DSH]\n';
|
|
12
9
|
const DSH_ASSISTANT_PREFIX = '[DSH 助手]\n';
|
|
@@ -159,15 +156,19 @@ export function createSessionSyncCoordinator({ deliveryService, logger = console
|
|
|
159
156
|
turns.delete(sessionId);
|
|
160
157
|
if (state.origin !== 'dsh' || !completedTurn(event.data?.reason)
|
|
161
158
|
|| !state.recipients?.size || !state.assistant.text) return;
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
159
|
+
// A pending card is not proof of delivery. Only suppress this target's
|
|
160
|
+
// text after its renderer confirms the complete final answer is visible.
|
|
161
|
+
// Targets without a renderer retain the original text delivery path.
|
|
162
|
+
const recipients = [...state.recipients.values()];
|
|
163
|
+
const rendered = await Promise.all(recipients.map(async (target) => {
|
|
164
|
+
try {
|
|
165
|
+
return await deliverSessionSyncMirror(target, sessionId, state.turn, state.assistant.text);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
logFailure('card delivery', target, error);
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}));
|
|
171
|
+
const plain = recipients.filter((_target, index) => !rendered[index]);
|
|
171
172
|
if (plain.length === 0) return;
|
|
172
173
|
await deliver(
|
|
173
174
|
sessionId,
|