@wowyuarm/dsh-agent-team 0.1.4 → 0.1.5
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.zh.md +5 -2
- package/package.json +1 -1
- package/packages/agent-team/lib/auto-compaction.js +39 -1
- package/packages/agent-team/lib/index.js +85 -5
- package/packages/agent-team/lib/ledger.js +80 -2
- package/packages/agent-team/lib/spec.js +12 -0
- package/packages/agent-team/lib/typert.host.js +94 -22
- package/packages/agent-team/lib/typert.remote-client.d.ts +3 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +79 -22
- package/packages/agent-team/lib/types/auto-compaction.d.ts +9 -0
- package/packages/agent-team/lib/types/auto-compaction.d.ts.map +1 -1
- package/packages/agent-team/lib/types/index.d.ts +13 -1
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +20 -0
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types.d.ts +41 -1
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/client-agent-team/lib/client.js +486 -90
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +2 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +41 -5
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +20 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +3 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +73 -17
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +16 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +48 -18
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts +3 -2
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts +5 -2
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -4
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +21 -2
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +4 -2
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +15 -13
- package/packages/client-agent-team/lib/types/client/locales.d.ts +16 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +16 -0
- package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts +16 -0
- package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/sidebar-sections.js +102 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +2 -1
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.js +9 -2
|
@@ -9,6 +9,7 @@ interface TeamAgentsPanelProps {
|
|
|
9
9
|
readonly addMember: TeamSidebarProps['addMember'];
|
|
10
10
|
readonly updateMember: TeamSidebarProps['updateMember'];
|
|
11
11
|
readonly recoverMember: TeamSidebarProps['recoverMember'];
|
|
12
|
+
readonly clearMemberContext: TeamSidebarProps['clearMemberContext'];
|
|
12
13
|
readonly joinChannel: TeamSidebarProps['joinChannel'];
|
|
13
14
|
readonly removeChannelMember: TeamSidebarProps['removeChannelMember'];
|
|
14
15
|
readonly loadModels: TeamSidebarProps['loadModels'];
|
|
@@ -18,6 +19,6 @@ interface TeamAgentsPanelProps {
|
|
|
18
19
|
readonly onCreatingChange: (request: AgentTeamAddMemberRequest, creating: boolean) => void;
|
|
19
20
|
readonly t: TeamSidebarProps['t'];
|
|
20
21
|
}
|
|
21
|
-
export declare function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, recoverMember, joinChannel, removeChannelMember, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }: TeamAgentsPanelProps): import("react").JSX.Element;
|
|
22
|
+
export declare function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, recoverMember, clearMemberContext, joinChannel, removeChannelMember, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }: TeamAgentsPanelProps): import("react").JSX.Element;
|
|
22
23
|
export {};
|
|
23
24
|
//# sourceMappingURL=TeamAgentsPanel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamAgentsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAgentsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAG5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"TeamAgentsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAgentsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAG5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAalD,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAA;IAC/D,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAA;IACjD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,CAAA;IACnE,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,qBAAqB,CAAC,CAAA;IACrE,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,8EAA8E;IAC9E,QAAQ,CAAC,eAAe,CAAC,EAAE,2BAA2B,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAA;IAC7E,QAAQ,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,mBAAmB,CAAC,CAAA;IACjE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,OAAO,EAAE,yBAAyB,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAC1F,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC;AAED,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BAoLpR"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { Button, IconEditOutline16, IconPlayOutline16, IconPlusOutline16, IconRefreshOutline16, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
|
+
import { Button, IconEditOutline16, IconNewChatOutline16, IconPlayOutline16, IconPlusOutline16, IconRefreshOutline16, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { TeamMemberAvatar } from "./TeamMemberAvatar.js";
|
|
5
5
|
import { SortableRow, useSidebarRowDrag } from "./sidebar-drag.js";
|
|
6
6
|
import { moveSidebarItem, useSidebarOrder } from "./sidebar-order.js";
|
|
7
|
+
import { useSidebarSectionOpen, setSidebarSectionOpen } from "./sidebar-sections.js";
|
|
7
8
|
import { mintRequestId } from "./requests.js";
|
|
8
9
|
import { TeamRowMenu } from "./TeamRowMenu.js";
|
|
9
10
|
import { TeamSidebarSection } from "./TeamSidebarSection.js";
|
|
@@ -11,7 +12,7 @@ import { MultiMenuField } from "./multi-menu-field.js";
|
|
|
11
12
|
import { AgentEditorDialog, ModelPickerField, sameModel } from "./TeamMemberEditor.js";
|
|
12
13
|
import createCss from './create.module.css';
|
|
13
14
|
import css from './sidebar.module.css';
|
|
14
|
-
export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, recoverMember, joinChannel, removeChannelMember, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }) {
|
|
15
|
+
export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, recoverMember, clearMemberContext, joinChannel, removeChannelMember, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }) {
|
|
15
16
|
const [members, setMembers] = useState([]);
|
|
16
17
|
const [channels, setChannels] = useState([]);
|
|
17
18
|
const [channelRefs, setChannelRefs] = useState([]);
|
|
@@ -36,6 +37,7 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
|
|
|
36
37
|
void moveSidebarItem(workspaceId, 'agents', orderedAgentRefs, movedRef, targetRef, marker);
|
|
37
38
|
};
|
|
38
39
|
const drag = useSidebarRowDrag({ refs: orderedAgentRefs, onCommit: applyMove });
|
|
40
|
+
const sectionOpen = useSidebarSectionOpen(workspaceId, 'agents');
|
|
39
41
|
const refresh = useCallback(async () => {
|
|
40
42
|
setLoading(true);
|
|
41
43
|
const result = await loadMembers({ workspaceId });
|
|
@@ -132,16 +134,17 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
|
|
|
132
134
|
return (_jsxs("div", { className: css.panel, children: [_jsx(Modal, { open: formOpen, onClose: closeForm, title: t('addAgent'), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: creating, onClick: closeForm, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-agent-create-form", variant: "primary", disabled: creating || handle.trim().length === 0, children: creating ? t('creatingAgent') : t('createAgent') })] }), children: _jsxs("form", { id: "team-agent-create-form", className: createCss.form, onSubmit: submit, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('agentName') }), _jsx(Input, { className: createCss.input, value: handle, onChange: event => { setHandle(event.target.value); setRetryRequest(undefined); }, disabled: creating, autoFocus: true })] }), _jsxs("label", { className: createCss.field, children: [_jsxs("span", { children: [t('agentDescription'), t('optionalSuffix')] }), _jsx(Input, { className: createCss.input, value: description, placeholder: t('agentDescriptionPlaceholder'), onChange: event => { setDescription(event.target.value); setRetryRequest(undefined); }, disabled: creating })] }), _jsx(ModelPickerField, { model: model, onModelChange: choice => { setModel(choice); setRetryRequest(undefined); }, loadModels: loadModels, disabled: creating, t: t }), _jsx(MultiMenuField, { label: `${t('initialChannels')}${t('optionalSuffix')}`, disabled: creating, options: channels.map(channel => ({ id: channel.channelRef, label: channel.name })), selected: channelRefs, onToggle: ref => {
|
|
133
135
|
setChannelRefs(current => current.includes(ref) ? current.filter(item => item !== ref) : [...current, ref]);
|
|
134
136
|
setRetryRequest(undefined);
|
|
135
|
-
}, emptyText: t('noChannelsForAgent'), triggerEmptyLabel: t('channelsPickerEmpty'), formatCount: count => t('channelsPickerCount', { count }) }), formOpen && error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('agents'), actions: (_jsx(Tooltip, { label: t('addAgent'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addAgent'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('loadingAgents') }), !loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('emptyAgents') }), _jsx("div", { className: css.agentList, children: orderedMembers.map(status => (_jsx(SortableRow, { drag: drag, orderKey: status.member.memberId, children: _jsx(AgentRow, { status: status, ...(memberSessionId === undefined ? {} : { current: status.member.sessionId === memberSessionId }), channels: channels, loadChannels: loadChannels, updateMember: updateMember, recoverMember: recoverMember, joinChannel: joinChannel, removeChannelMember: removeChannelMember, loadModels: loadModels, openMemberSession: openMemberSession, onUpdated: () => { void refresh(); }, t: t }) }, status.member.memberId))) })] }), !formOpen && error !== undefined && (_jsxs("div", { className: css.retryError, role: "alert", children: [_jsx("span", { children: error }), retryRequest !== undefined && _jsx("button", { type: "button", className: css.textButton, disabled: creating, onClick: () => { setFormOpen(true); }, children: t('retry') })] }))] }));
|
|
137
|
+
}, emptyText: t('noChannelsForAgent'), triggerEmptyLabel: t('channelsPickerEmpty'), formatCount: count => t('channelsPickerCount', { count }) }), formOpen && error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('agents'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'agents', open); }, actions: (_jsx(Tooltip, { label: t('addAgent'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addAgent'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('loadingAgents') }), !loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('emptyAgents') }), _jsx("div", { className: css.agentList, children: orderedMembers.map(status => (_jsx(SortableRow, { drag: drag, orderKey: status.member.memberId, children: _jsx(AgentRow, { status: status, ...(memberSessionId === undefined ? {} : { current: status.member.sessionId === memberSessionId }), channels: channels, loadChannels: loadChannels, updateMember: updateMember, recoverMember: recoverMember, clearMemberContext: clearMemberContext, joinChannel: joinChannel, removeChannelMember: removeChannelMember, loadModels: loadModels, openMemberSession: openMemberSession, onUpdated: () => { void refresh(); }, t: t }) }, status.member.memberId))) })] }), !formOpen && error !== undefined && (_jsxs("div", { className: css.retryError, role: "alert", children: [_jsx("span", { children: error }), retryRequest !== undefined && _jsx("button", { type: "button", className: css.textButton, disabled: creating, onClick: () => { setFormOpen(true); }, children: t('retry') })] }))] }));
|
|
136
138
|
}
|
|
137
139
|
/**
|
|
138
140
|
* One sidebar Agent row: the select button opens the Member's own Session
|
|
139
141
|
* conversation page, the avatar carries identity plus the presence badge, and
|
|
140
142
|
* the row menu opens the editor.
|
|
141
143
|
*/
|
|
142
|
-
function AgentRow({ status, current, channels, loadChannels, updateMember, recoverMember, joinChannel, removeChannelMember, loadModels, openMemberSession, onUpdated, t }) {
|
|
144
|
+
function AgentRow({ status, current, channels, loadChannels, updateMember, recoverMember, clearMemberContext, joinChannel, removeChannelMember, loadModels, openMemberSession, onUpdated, t }) {
|
|
143
145
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
144
146
|
const [editing, setEditing] = useState(false);
|
|
147
|
+
const [clearing, setClearing] = useState(false);
|
|
145
148
|
const [rowAlert, setRowAlert] = useState();
|
|
146
149
|
// Both row actions ride the same runtime remote: the Host steers a live
|
|
147
150
|
// session, rebuilds an orphaned composition, or re-runs a failed activation.
|
|
@@ -171,14 +174,47 @@ function AgentRow({ status, current, channels, loadChannels, updateMember, recov
|
|
|
171
174
|
setRowAlert(t('restartFailed', { message: cause instanceof Error ? cause.message : String(cause) }));
|
|
172
175
|
}
|
|
173
176
|
};
|
|
177
|
+
const clearContext = async () => {
|
|
178
|
+
try {
|
|
179
|
+
const result = await clearMemberContext({
|
|
180
|
+
requestId: mintRequestId(),
|
|
181
|
+
workspaceId: status.member.workspaceId,
|
|
182
|
+
memberId: status.member.memberId,
|
|
183
|
+
});
|
|
184
|
+
await onUpdated();
|
|
185
|
+
if (!result.ok) {
|
|
186
|
+
setRowAlert(t('clearContextFailed', { message: result.error.message }));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
setRowAlert(undefined);
|
|
190
|
+
// The Host moved the Member onto a fresh Session id (the previous log
|
|
191
|
+
// stays archived on disk). A new id has no resident client instance, so
|
|
192
|
+
// re-entering the member view lazily instantiates it from host truth:
|
|
193
|
+
// blank hero, live updates, and no disposed-generation gray-out.
|
|
194
|
+
if (current === true)
|
|
195
|
+
openMemberSession(result.value.status.member.sessionId);
|
|
196
|
+
}
|
|
197
|
+
catch (cause) {
|
|
198
|
+
setRowAlert(t('clearContextFailed', { message: cause instanceof Error ? cause.message : String(cause) }));
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
// Error Members keep a live idle handle, so starting from a new context
|
|
202
|
+
// is available to them too — it doubles as a recovery path (the broken
|
|
203
|
+
// handle's error markers are dropped with it). Only a running turn or a
|
|
204
|
+
// missing handle gates the entry.
|
|
205
|
+
const contextClearable = status.presence === 'available' || status.presence === 'error';
|
|
174
206
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.agentRow, "data-menu-open": menuOpen || undefined, children: [_jsxs("button", { type: "button", className: css.agentSelect, "aria-label": t('openAgentSession', { name: status.member.handle }), "aria-current": current ? 'page' : undefined, disabled: status.availability !== 'active', onClick: () => { openMemberSession(status.member.sessionId); }, children: [_jsx(TeamMemberAvatar, { status: status, t: t }), _jsxs("span", { className: css.agentCopy, children: [_jsx("strong", { children: status.member.handle }), _jsx("small", { children: status.member.description })] })] }), _jsx("span", { className: css.rowMenu, children: _jsx(TeamRowMenu, { label: t('actionsAgent', { name: status.member.handle }), items: [
|
|
175
207
|
{ id: 'edit', label: t('editAgent'), icon: _jsx(IconEditOutline16, {}) },
|
|
176
208
|
...(status.presence === 'error' ? [{ id: 'resume', label: t('resumeAgent'), icon: _jsx(IconPlayOutline16, {}) }] : []),
|
|
177
209
|
...(status.availability === 'unavailable' ? [{ id: 'restart', label: t('restartAgent'), icon: _jsx(IconRefreshOutline16, {}) }] : []),
|
|
210
|
+
{ id: 'clear-context', label: t('clearContextAgent'), icon: _jsx(IconNewChatOutline16, {}), danger: true, disabled: !contextClearable },
|
|
211
|
+
...(!contextClearable ? [{ type: 'label', id: 'clear-context-reason', text: status.presence === 'working' ? t('clearContextWorkingReason') : t('clearContextUnavailableReason') }] : []),
|
|
178
212
|
], onSelect: (id) => {
|
|
179
213
|
if (id === 'edit')
|
|
180
214
|
setEditing(true);
|
|
215
|
+
else if (id === 'clear-context')
|
|
216
|
+
setClearing(true);
|
|
181
217
|
else
|
|
182
218
|
void recover();
|
|
183
|
-
}, onOpenChange: setMenuOpen }) })] }), rowAlert !== undefined && _jsx("div", { className: css.rowAlert, role: "alert", children: rowAlert }), editing && (_jsx(AgentEditorDialog, { status: status, channels: channels, loadChannels: loadChannels, updateMember: updateMember, joinChannel: joinChannel, removeChannelMember: removeChannelMember, loadModels: loadModels, onCommitted: onUpdated, onClose: () => { setEditing(false); }, t: t }))] }));
|
|
219
|
+
}, onOpenChange: setMenuOpen }) })] }), rowAlert !== undefined && _jsx("div", { className: css.rowAlert, role: "alert", children: rowAlert }), clearing && (_jsx(Modal, { open: true, onClose: () => { setClearing(false); }, title: t('clearContextTitle', { name: status.member.handle }), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: () => { setClearing(false); }, children: t('cancel') }), _jsx(Button, { variant: "primary", onClick: () => { setClearing(false); void clearContext(); }, children: t('clearContextConfirm') })] }), children: _jsx("p", { className: createCss.error, children: t('clearContextNotice', { name: status.member.handle }) }) })), editing && (_jsx(AgentEditorDialog, { status: status, channels: channels, loadChannels: loadChannels, updateMember: updateMember, joinChannel: joinChannel, removeChannelMember: removeChannelMember, loadModels: loadModels, onCommitted: onUpdated, onClose: () => { setEditing(false); }, t: t }))] }));
|
|
184
220
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamChannelPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA+B,mBAAmB,EAE7D,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAIvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAc/D,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAA;IACxC,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAA;IACpE,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,mBAAmB,EAAE,qBAAqB,CAAC,qBAAqB,CAAC,CAAA;IAC1E,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,cAAc,EAAE,qBAAqB,CAAC,gBAAgB,CAAC,CAAA;IAChE,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AAmBD,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC,EAAE,EAAE,oBAAoB,+
|
|
1
|
+
{"version":3,"file":"TeamChannelPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA+B,mBAAmB,EAE7D,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAIvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAc/D,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAA;IACxC,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAA;IACpE,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,mBAAmB,EAAE,qBAAqB,CAAC,qBAAqB,CAAC,CAAA;IAC1E,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,cAAc,EAAE,qBAAqB,CAAC,gBAAgB,CAAC,CAAA;IAChE,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AAmBD,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BAmVnR"}
|
|
@@ -60,6 +60,26 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
60
60
|
selectChannel(ref);
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
|
+
if (ref.startsWith('thread:')) {
|
|
64
|
+
const match = view?.items.find(item => item.thread.threadRef === ref);
|
|
65
|
+
if (match !== undefined) {
|
|
66
|
+
selectThread(match.thread.threadRef, channelRef, match.task?.taskRef, match.taskNumber);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// A ref may point outside the currently loaded Channel window. Ask the
|
|
70
|
+
// Host for the bounded Thread view so it can provide the home Channel.
|
|
71
|
+
void loadChannels({ workspaceId, threadRef: ref, includeActivities: false, limit: 1 }).then(result => {
|
|
72
|
+
if (!result.ok)
|
|
73
|
+
return;
|
|
74
|
+
const target = result.value.items[0];
|
|
75
|
+
if (target !== undefined) {
|
|
76
|
+
const targetChannel = result.value.channels.find(channel => channel.channelRef === target.message.channelRef);
|
|
77
|
+
if (targetChannel !== undefined)
|
|
78
|
+
selectThread(target.thread.threadRef, targetChannel.channelRef, target.task?.taskRef, target.taskNumber);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
63
83
|
if (ref.startsWith('task:')) {
|
|
64
84
|
const match = view?.items.find(item => item.task?.taskRef === ref);
|
|
65
85
|
if (match !== undefined) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamChannelsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EAEzB,mBAAmB,EAMpB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"TeamChannelsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EAEzB,mBAAmB,EAMpB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAalD,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,wGAAwG;IACxG,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAA;IAC/D,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,qBAAqB,CAAC,CAAA;IACrE,QAAQ,CAAC,cAAc,EAAE,SAAS,yBAAyB,EAAE,CAAA;IAC7D,QAAQ,CAAC,kBAAkB,CAAC,EAAE,mBAAmB,CAAA;IACjD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,+BA8L9D"}
|
|
@@ -5,6 +5,7 @@ import { TeamPresenceDot } from "./TeamPresenceDot.js";
|
|
|
5
5
|
import { MultiMenuField } from "./multi-menu-field.js";
|
|
6
6
|
import { SortableRow, useSidebarRowDrag } from "./sidebar-drag.js";
|
|
7
7
|
import { moveSidebarItem, useSidebarOrder } from "./sidebar-order.js";
|
|
8
|
+
import { useSidebarSectionOpen, setSidebarSectionOpen } from "./sidebar-sections.js";
|
|
8
9
|
import { mintRequestId } from "./requests.js";
|
|
9
10
|
import { TeamRowMenu } from "./TeamRowMenu.js";
|
|
10
11
|
import { TeamSidebarSection } from "./TeamSidebarSection.js";
|
|
@@ -37,6 +38,7 @@ export function TeamChannelsPanel(props) {
|
|
|
37
38
|
void moveSidebarItem(workspaceId, 'channels', orderedChannelRefs, movedRef, targetRef, marker);
|
|
38
39
|
};
|
|
39
40
|
const drag = useSidebarRowDrag({ refs: orderedChannelRefs, onCommit: applyMove });
|
|
41
|
+
const sectionOpen = useSidebarSectionOpen(workspaceId, 'channels');
|
|
40
42
|
const refresh = useCallback(async () => {
|
|
41
43
|
setLoading(true);
|
|
42
44
|
const [channelResult, memberResult] = await Promise.all([
|
|
@@ -144,7 +146,7 @@ export function TeamChannelsPanel(props) {
|
|
|
144
146
|
return next;
|
|
145
147
|
});
|
|
146
148
|
setPendingCreate(undefined);
|
|
147
|
-
}, triggerEmptyLabel: t('membersPickerEmpty'), formatCount: count => t('membersPickerCount', { count }) }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('channels'), actions: (_jsx(Tooltip, { label: t('addChannel'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addChannel'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && view === undefined && _jsx("p", { className: css.emptyState, children: t('loadingChannels') }), !loading && (view?.channels.length ?? 0) === 0 && _jsx("p", { className: css.emptyState, children: t('emptyChannels') }), _jsx("div", { className: css.channelList, children: orderedChannels.map(channel => {
|
|
149
|
+
}, triggerEmptyLabel: t('membersPickerEmpty'), formatCount: count => t('membersPickerCount', { count }) }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('channels'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'channels', open); }, actions: (_jsx(Tooltip, { label: t('addChannel'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addChannel'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && view === undefined && _jsx("p", { className: css.emptyState, children: t('loadingChannels') }), !loading && (view?.channels.length ?? 0) === 0 && _jsx("p", { className: css.emptyState, children: t('emptyChannels') }), _jsx("div", { className: css.channelList, children: orderedChannels.map(channel => {
|
|
148
150
|
const joined = membership.get(channel.channelRef) ?? new Set();
|
|
149
151
|
return (_jsx(SortableRow, { drag: drag, orderKey: channel.channelRef, children: _jsx(ChannelRow, { channel: channel, members: members, joinedIds: joined, selected: selectedChannelRef === channel.channelRef, updateChannel: updateChannel, joinChannel: props.joinChannel, removeChannelMember: props.removeChannelMember, onCommitted: () => { void refresh(); }, selectChannel: selectChannel, t: t }) }, channel.channelRef));
|
|
150
152
|
}) })] }), !formOpen && error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamComposer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAEpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"TeamComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamComposer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAEpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AA8DvD,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,EAAE,EAAE;IAC3M,QAAQ,CAAC,OAAO,EAAE,SAAS,2BAA2B,EAAE,CAAA;IACxD,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAA;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/C,QAAQ,CAAC,kBAAkB,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAA;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,gGAAgG;IAChG,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,IAAI,EAAE,CAAA;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,KAAK,IAAI,CAAA;IACzD,gFAAgF;IAChF,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;IACnD,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC,+BAqSA"}
|
|
@@ -30,6 +30,13 @@ function mentionCandidates(members, query) {
|
|
|
30
30
|
&& status.member.state !== 'inactive'
|
|
31
31
|
&& status.member.handle.toLocaleLowerCase().startsWith(normalized));
|
|
32
32
|
}
|
|
33
|
+
/** Every member a manual handle pick could reach: the @all expansion snapshot. */
|
|
34
|
+
function allMentionMembers(members) {
|
|
35
|
+
return members.filter(status => status.presence !== 'unavailable' && status.member.state !== 'inactive');
|
|
36
|
+
}
|
|
37
|
+
function containsAllMention(draft) {
|
|
38
|
+
return /(?:^|[^\p{L}\p{N}_])@all(?=$|[^\p{L}\p{N}_])/u.test(draft);
|
|
39
|
+
}
|
|
33
40
|
/** One object URL per draft file; revoked when the draft is removed. */
|
|
34
41
|
const draftPreviewUrls = new WeakMap();
|
|
35
42
|
function draftPreviewUrl(file) {
|
|
@@ -50,9 +57,15 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
|
|
|
50
57
|
const composingRef = useRef(false);
|
|
51
58
|
const [mention, setMention] = useState();
|
|
52
59
|
const [highlight, setHighlight] = useState(0);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
60
|
+
// @all is a composer-layer expansion of the same eligibility filter a
|
|
61
|
+
// handle pick uses; the fixed row stays on top of the matching members.
|
|
62
|
+
const memberCandidates = mention === undefined ? [] : mentionCandidates(members, mention.query);
|
|
63
|
+
const options = mention !== undefined && 'all'.startsWith(mention.query.toLocaleLowerCase())
|
|
64
|
+
? [{ kind: 'all' }, ...memberCandidates.map(status => ({ kind: 'member', status }))]
|
|
65
|
+
: memberCandidates.map(status => ({ kind: 'member', status }));
|
|
66
|
+
const menuOpen = mention !== undefined && options.length > 0;
|
|
67
|
+
const activeOption = options[highlight];
|
|
68
|
+
const allCount = allMentionMembers(members).length;
|
|
56
69
|
const listId = 'team-mention-suggestions';
|
|
57
70
|
const menuMaxHeight = useAnchoredMaxHeight(menuRef, 320, menuOpen ? draft : null);
|
|
58
71
|
useDismissOnOutsidePointer(rootRef, menuOpen, open => { if (!open)
|
|
@@ -65,12 +78,12 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
|
|
|
65
78
|
input.style.height = `${Math.min(input.scrollHeight, 180)}px`;
|
|
66
79
|
}, [draft]);
|
|
67
80
|
useEffect(() => {
|
|
68
|
-
if (mention === undefined ||
|
|
81
|
+
if (mention === undefined || options.length === 0) {
|
|
69
82
|
setHighlight(0);
|
|
70
83
|
return;
|
|
71
84
|
}
|
|
72
|
-
setHighlight(current => Math.min(current,
|
|
73
|
-
}, [mention,
|
|
85
|
+
setHighlight(current => Math.min(current, options.length - 1));
|
|
86
|
+
}, [mention, options.length]);
|
|
74
87
|
// Match the resident DSH composer without stealing a later user choice: Team
|
|
75
88
|
// data can load after navigation, so a dialog or another control may already
|
|
76
89
|
// own focus by the time this composer appears.
|
|
@@ -88,6 +101,10 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
|
|
|
88
101
|
inputRef.current?.focus({ preventScroll: true });
|
|
89
102
|
}, [confirmation, pending]);
|
|
90
103
|
const pruneRecipients = (nextDraft) => {
|
|
104
|
+
// An @all marker stands for its expansion snapshot: the member handles it
|
|
105
|
+
// stands for are not in the text, so text-based pruning must stand down.
|
|
106
|
+
if (containsAllMention(nextDraft))
|
|
107
|
+
return;
|
|
91
108
|
const knownMembers = new Map(members.map(status => [status.member.memberId, status.member]));
|
|
92
109
|
const next = new Set([...recipients].filter(memberId => {
|
|
93
110
|
const member = knownMembers.get(memberId);
|
|
@@ -117,9 +134,31 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
|
|
|
117
134
|
pruneRecipients(nextDraft);
|
|
118
135
|
updateMention(nextDraft, event.target.selectionStart ?? nextDraft.length);
|
|
119
136
|
};
|
|
120
|
-
const
|
|
137
|
+
const selectOption = (option) => {
|
|
121
138
|
if (mention === undefined)
|
|
122
139
|
return;
|
|
140
|
+
if (option.kind === 'all') {
|
|
141
|
+
// Expand at pick time: the recipients snapshot is every eligible member
|
|
142
|
+
// a handle pick could reach at this moment.
|
|
143
|
+
const nextDraft = `${draft.slice(0, mention.start)}@all ${draft.slice(mention.end)}`;
|
|
144
|
+
const nextCaret = mention.start + '@all '.length;
|
|
145
|
+
const nextRecipients = new Set(recipients);
|
|
146
|
+
for (const status of allMentionMembers(members))
|
|
147
|
+
nextRecipients.add(status.member.memberId);
|
|
148
|
+
onDraftChange(nextDraft);
|
|
149
|
+
onRecipientsChange(nextRecipients);
|
|
150
|
+
setMention(undefined);
|
|
151
|
+
setHighlight(0);
|
|
152
|
+
requestAnimationFrame(() => {
|
|
153
|
+
const input = inputRef.current;
|
|
154
|
+
if (input === null)
|
|
155
|
+
return;
|
|
156
|
+
input.focus({ preventScroll: true });
|
|
157
|
+
input.setSelectionRange(nextCaret, nextCaret);
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const member = option.status;
|
|
123
162
|
const inserted = `@${member.member.handle} `;
|
|
124
163
|
const nextDraft = `${draft.slice(0, mention.start)}${inserted}${draft.slice(mention.end)}`;
|
|
125
164
|
const nextCaret = mention.start + inserted.length;
|
|
@@ -136,16 +175,31 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
|
|
|
136
175
|
input.setSelectionRange(nextCaret, nextCaret);
|
|
137
176
|
});
|
|
138
177
|
};
|
|
178
|
+
// Pasted files (screenshots, copies) join the same chips the "+" picker
|
|
179
|
+
// fills; only a paste that carries files is intercepted, so plain text
|
|
180
|
+
// keeps the browser's native insertion.
|
|
181
|
+
const onPaste = (event) => {
|
|
182
|
+
if (pending || onFilesChange === undefined || pendingFiles === undefined)
|
|
183
|
+
return;
|
|
184
|
+
const files = Array.from(event.clipboardData.items)
|
|
185
|
+
.filter(item => item.kind === 'file')
|
|
186
|
+
.map(item => item.getAsFile())
|
|
187
|
+
.filter((file) => file !== null);
|
|
188
|
+
if (files.length === 0)
|
|
189
|
+
return;
|
|
190
|
+
event.preventDefault();
|
|
191
|
+
onFilesChange([...pendingFiles, ...files]);
|
|
192
|
+
};
|
|
139
193
|
const onKeyDown = (event) => {
|
|
140
194
|
const composing = composingRef.current || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;
|
|
141
195
|
if (menuOpen && event.key === 'ArrowDown') {
|
|
142
196
|
event.preventDefault();
|
|
143
|
-
setHighlight(current => (current + 1) %
|
|
197
|
+
setHighlight(current => (current + 1) % options.length);
|
|
144
198
|
return;
|
|
145
199
|
}
|
|
146
200
|
if (menuOpen && event.key === 'ArrowUp') {
|
|
147
201
|
event.preventDefault();
|
|
148
|
-
setHighlight(current => (current - 1 +
|
|
202
|
+
setHighlight(current => (current - 1 + options.length) % options.length);
|
|
149
203
|
return;
|
|
150
204
|
}
|
|
151
205
|
if (event.key === 'Escape' && menuOpen) {
|
|
@@ -156,15 +210,15 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
|
|
|
156
210
|
// Tab accepts the highlighted candidate; Shift+Tab keeps default focus reversal.
|
|
157
211
|
if (menuOpen && event.key === 'Tab' && !event.shiftKey) {
|
|
158
212
|
event.preventDefault();
|
|
159
|
-
if (
|
|
160
|
-
|
|
213
|
+
if (activeOption !== undefined)
|
|
214
|
+
selectOption(activeOption);
|
|
161
215
|
return;
|
|
162
216
|
}
|
|
163
217
|
if (event.key !== 'Enter' || event.shiftKey || composing || event.repeat)
|
|
164
218
|
return;
|
|
165
|
-
if (menuOpen &&
|
|
219
|
+
if (menuOpen && activeOption !== undefined) {
|
|
166
220
|
event.preventDefault();
|
|
167
|
-
|
|
221
|
+
selectOption(activeOption);
|
|
168
222
|
return;
|
|
169
223
|
}
|
|
170
224
|
event.preventDefault();
|
|
@@ -175,11 +229,13 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
|
|
|
175
229
|
event.preventDefault();
|
|
176
230
|
if (!pending && draft.trim() !== '')
|
|
177
231
|
onSubmit();
|
|
178
|
-
}, children: [_jsxs("div", { className: css.card, "data-team-composer": true, children: [confirmation !== undefined && _jsx("p", { className: css.confirmation, role: "status", children: confirmation }), _jsxs("div", { className: css.inputArea, children: [menuOpen && _jsx("div", { id: listId, ref: menuRef, className: css.mentionMenu, role: "listbox", "aria-label": t('mentionSuggestions'), style: { maxHeight: menuMaxHeight }, children:
|
|
179
|
-
const optionId = `${listId}-${status.member.memberId}`;
|
|
232
|
+
}, children: [_jsxs("div", { className: css.card, "data-team-composer": true, children: [confirmation !== undefined && _jsx("p", { className: css.confirmation, role: "status", children: confirmation }), _jsxs("div", { className: css.inputArea, children: [menuOpen && _jsx("div", { id: listId, ref: menuRef, className: css.mentionMenu, role: "listbox", "aria-label": t('mentionSuggestions'), style: { maxHeight: menuMaxHeight }, children: options.map((option, index) => {
|
|
233
|
+
const optionId = option.kind === 'all' ? `${listId}-all` : `${listId}-${option.status.member.memberId}`;
|
|
180
234
|
const selected = index === highlight;
|
|
181
|
-
return
|
|
182
|
-
|
|
235
|
+
return option.kind === 'all'
|
|
236
|
+
? _jsxs("button", { id: optionId, type: "button", role: "option", "aria-selected": selected, className: css.mentionOption, onMouseDown: event => { event.preventDefault(); }, onClick: () => { selectOption(option); }, children: [_jsx("span", { className: css.mentionAllDot, "aria-hidden": "true" }), _jsx("span", { className: css.mentionName, children: "@all" }), _jsx("span", { className: css.mentionDescription, children: t('mentionAll', { count: allCount }) })] }, "all")
|
|
237
|
+
: _jsxs("button", { id: optionId, type: "button", role: "option", "aria-selected": selected, className: css.mentionOption, onMouseDown: event => { event.preventDefault(); }, onClick: () => { selectOption(option); }, children: [_jsx(TeamPresenceDot, { status: option.status, t: t }), _jsxs("span", { className: css.mentionName, children: ["@", option.status.member.handle] }), _jsx("span", { className: css.mentionDescription, children: option.status.member.description })] }, option.status.member.memberId);
|
|
238
|
+
}) }), _jsx("textarea", { ref: inputRef, "aria-label": t('messageDraft'), "aria-autocomplete": "list", "aria-controls": menuOpen ? listId : undefined, "aria-activedescendant": menuOpen && activeOption !== undefined ? activeOption.kind === 'all' ? `${listId}-all` : `${listId}-${activeOption.status.member.memberId}` : undefined, "aria-expanded": menuOpen, value: draft, readOnly: pending, placeholder: placeholder ?? t('messagePlaceholder'), rows: 1, onChange: onChange, onPaste: onPaste, onKeyDown: onKeyDown, onSelect: event => { updateMention(event.currentTarget.value, event.currentTarget.selectionStart ?? event.currentTarget.value.length); }, onCompositionStart: () => { composingRef.current = true; }, onCompositionEnd: () => { setTimeout(() => { composingRef.current = false; }, 10); } })] }), recipients.size > 0 && _jsx("p", { className: css.notifyRow, "data-team-notify": true, children: t('composerNotify', { ids: [...recipients].sort().map(memberId => `@${members.find(candidate => candidate.member.memberId === memberId)?.member.handle ?? memberId}`).join(', ') }) }), onFilesChange !== undefined && pendingFiles !== undefined && pendingFiles.length > 0 && (_jsx("ul", { className: css.fileChips, "aria-label": t('attachFiles'), children: pendingFiles.map((file, index) => {
|
|
183
239
|
const previewUrl = draftPreviewUrl(file);
|
|
184
240
|
return _jsxs("li", { className: `${css.fileChip} ${previewUrl !== undefined ? css.imageChip : ''}`, children: [previewUrl !== undefined && _jsx("img", { src: previewUrl, alt: "", className: css.imageChipPreview }), _jsxs("span", { className: css.fileChipName, title: file.name, children: [file.name, _jsx("span", { className: css.fileChipSize, children: formatByteSize(file.size) })] }), _jsx("button", { type: "button", className: css.fileChipRemove, "aria-label": t('removeFile', { name: file.name }), disabled: pending, onClick: () => {
|
|
185
241
|
const url = draftPreviewUrls.get(file);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamMemberSessionComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMemberSessionComposer.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,KAAK,EAAE,sBAAsB,EAAc,MAAM,iDAAiD,CAAA;AAIzG,+EAA+E;AAC/E,MAAM,MAAM,8BAA8B,GAAG,YAAY,CAAC,2BAA2B,CAAC,GAAG;IACvF,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAA;IAC3C,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAA;IACxC,wEAAwE;IACxE,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,KAAK,IAAI,CAAA;CACxD,CAAA;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,8BAA8B,+
|
|
1
|
+
{"version":3,"file":"TeamMemberSessionComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMemberSessionComposer.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,KAAK,EAAE,sBAAsB,EAAc,MAAM,iDAAiD,CAAA;AAIzG,+EAA+E;AAC/E,MAAM,MAAM,8BAA8B,GAAG,YAAY,CAAC,2BAA2B,CAAC,GAAG;IACvF,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAA;IAC3C,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAA;IACxC,wEAAwE;IACxE,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,KAAK,IAAI,CAAA;CACxD,CAAA;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,8BAA8B,+BA4HvL"}
|
|
@@ -45,8 +45,13 @@ export function TeamMemberSessionComposer({ controller, inputActions, overlay, p
|
|
|
45
45
|
setSubmissionNotice('Compact 未执行,请重试。');
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
|
-
if (input.phase === 'plain')
|
|
48
|
+
if (input.phase === 'plain') {
|
|
49
|
+
// Reaching plain after a compact flight means the InputMachine adopted
|
|
50
|
+
// the clear-on-success draft; a stale retry notice must not outlive it.
|
|
51
|
+
if (compactWasSubmitting.current)
|
|
52
|
+
setSubmissionNotice(undefined);
|
|
49
53
|
compactWasSubmitting.current = false;
|
|
54
|
+
}
|
|
50
55
|
}, [input?.claim?.token, input?.phase]);
|
|
51
56
|
useEffect(() => {
|
|
52
57
|
if (input === undefined)
|
|
@@ -86,6 +91,16 @@ export function TeamMemberSessionComposer({ controller, inputActions, overlay, p
|
|
|
86
91
|
return;
|
|
87
92
|
if (event.repeat || composing.current || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229)
|
|
88
93
|
return;
|
|
94
|
+
// Tab accepts the highlighted candidate, mirroring the team-mode composer
|
|
95
|
+
// and the Channel/Thread bars; Shift+Tab keeps native focus reversal.
|
|
96
|
+
if (event.key === 'Tab' && !event.shiftKey) {
|
|
97
|
+
const highlight = controller.menu.getSnapshot().highlight;
|
|
98
|
+
if (highlight !== null) {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
controller.pick(highlight.source, highlight.index);
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
89
104
|
const key = event.key === 'ArrowUp' ? 'up' : event.key === 'ArrowDown' ? 'down' : event.key === 'Escape' ? 'escape' : event.key === 'Enter' ? 'enter' : undefined;
|
|
90
105
|
if (key !== undefined) {
|
|
91
106
|
const outcome = controller.arbitrate(key, false);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamMessage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2F,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAE/H,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AACrH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,EAA4E,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAI/H,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,uFAAuF;IACvF,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B;2DACuD;IACvD,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,0BAA0B,EAAE,GAAG,SAAS,CAAA;IACxE,6EAA6E;IAC7E,QAAQ,CAAC,cAAc,CAAC,EAAE,qBAAqB,CAAC,eAAe,CAAC,GAAG,SAAS,CAAA;IAC5E,QAAQ,CAAC,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;IACnD,0FAA0F;IAC1F,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACxD,sFAAsF;IACtF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,KAAK,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IACzH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAC9B;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+
|
|
1
|
+
{"version":3,"file":"TeamMessage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2F,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAE/H,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AACrH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,EAA4E,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAI/H,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,uFAAuF;IACvF,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B;2DACuD;IACvD,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,0BAA0B,EAAE,GAAG,SAAS,CAAA;IACxE,6EAA6E;IAC7E,QAAQ,CAAC,cAAc,CAAC,EAAE,qBAAqB,CAAC,eAAe,CAAC,GAAG,SAAS,CAAA;IAC5E,QAAQ,CAAC,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;IACnD,0FAA0F;IAC1F,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACxD,sFAAsF;IACtF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,KAAK,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IACzH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAC9B;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BA8G3N"}
|
|
@@ -29,6 +29,7 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
|
|
|
29
29
|
void resolveUnknownTaskRefs(bodyTaskRefs, onResolveTaskRefs);
|
|
30
30
|
}, [onResolveTaskRefs, bodyTaskRefKey, refVersion]);
|
|
31
31
|
const taskLabel = useCallback((taskNumber) => t?.('taskLabel', { number: taskNumber }) ?? `Task #${taskNumber}`, [t]);
|
|
32
|
+
const refLabel = useCallback((ref) => ref.startsWith('thread:') ? (t?.('threadLabel') ?? 'Thread') : ref.startsWith('channel:') ? (t?.('channels') ?? 'Channels') : ref, [t]);
|
|
32
33
|
// Long bodies start clamped behind the expand control. The default derives
|
|
33
34
|
// from the body alone; the toggle itself is per-mount view state nothing
|
|
34
35
|
// persists.
|
|
@@ -49,16 +50,16 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
|
|
|
49
50
|
if (onResolveTaskRefs !== undefined && taskRefs.length > 0)
|
|
50
51
|
void resolveUnknownTaskRefs(taskRefs, onResolveTaskRefs);
|
|
51
52
|
for (const code of styledRefCodes)
|
|
52
|
-
renderResolvedMarkdownCodeRef(code, taskLabel);
|
|
53
|
+
renderResolvedMarkdownCodeRef(code, taskLabel, refLabel);
|
|
53
54
|
for (const node of textNodes)
|
|
54
|
-
renderResolvedMarkdownText(node, mentionNames ?? [], taskLabel);
|
|
55
|
+
renderResolvedMarkdownText(node, mentionNames ?? [], taskLabel, refLabel);
|
|
55
56
|
for (const button of root.querySelectorAll('button[data-task-ref]')) {
|
|
56
57
|
const taskRef = button.dataset.taskRef;
|
|
57
58
|
const hit = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
|
|
58
59
|
if (taskRef !== undefined && hit !== undefined)
|
|
59
60
|
button.textContent = taskLabel(hit.taskNumber);
|
|
60
61
|
}
|
|
61
|
-
}, [richAgentBody, onOpenRef, onResolveTaskRefs, refVersion, taskLabel, mentionNames]);
|
|
62
|
+
}, [richAgentBody, onOpenRef, onResolveTaskRefs, refVersion, taskLabel, refLabel, mentionNames]);
|
|
62
63
|
useEffect(() => {
|
|
63
64
|
const root = markdownRef.current;
|
|
64
65
|
if (!richAgentBody || root === null || onOpenRef === undefined)
|
|
@@ -67,12 +68,12 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
|
|
|
67
68
|
const target = event.target;
|
|
68
69
|
if (!(target instanceof Element))
|
|
69
70
|
return;
|
|
70
|
-
const button = target.closest('button[data-task-ref]');
|
|
71
|
+
const button = target.closest('button[data-ref], button[data-task-ref]');
|
|
71
72
|
if (button === null || !root.contains(button))
|
|
72
73
|
return;
|
|
73
|
-
const
|
|
74
|
-
if (
|
|
75
|
-
onOpenRef(
|
|
74
|
+
const ref = button instanceof HTMLElement ? button.dataset.ref ?? button.dataset.taskRef : undefined;
|
|
75
|
+
if (ref !== undefined)
|
|
76
|
+
onOpenRef(ref);
|
|
76
77
|
};
|
|
77
78
|
root.addEventListener('click', openTask);
|
|
78
79
|
return () => { root.removeEventListener('click', openTask); };
|
|
@@ -80,9 +81,9 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
|
|
|
80
81
|
const bodyNode = plan.render === 'inline' && plan.inline !== undefined
|
|
81
82
|
? _jsx("div", { className: css.messageText, children: plan.inline.segments.map((segment, index) => segment.mention
|
|
82
83
|
? _jsx("span", { className: css.mention, children: segment.text }, index)
|
|
83
|
-
: _jsx(Fragment, { children: renderRefs(segment.text, onOpenRef, taskLabel) }, index)) })
|
|
84
|
+
: _jsx(Fragment, { children: renderRefs(segment.text, onOpenRef, taskLabel, refLabel) }, index)) })
|
|
84
85
|
: plan.render === 'literal'
|
|
85
|
-
? _jsx("div", { className: css.messageText, children: onOpenRef === undefined ? _jsx(MessageText, { text: displayBody }) : renderRefs(displayBody, onOpenRef, taskLabel) })
|
|
86
|
+
? _jsx("div", { className: css.messageText, children: onOpenRef === undefined ? _jsx(MessageText, { text: displayBody }) : renderRefs(displayBody, onOpenRef, taskLabel, refLabel) })
|
|
86
87
|
: _jsx("div", { ref: markdownRef, className: css.messageMarkdown, children: _jsx(MarkdownText, { text: displayBody }, `${displayBody}:${onOpenRef === undefined ? 'literal' : 'refs'}`) });
|
|
87
88
|
return (_jsxs("article", { className: css.messageRow, "data-human": human || undefined, "data-grouped": grouped || undefined, children: [_jsx("div", { className: css.messageIdentity, style: avatarStyle, "aria-hidden": "true", children: senderName.replace('@', '').slice(0, 1).toUpperCase() }), _jsxs("div", { className: css.messageBody, children: [(!grouped || showGroupedTime === true) && (_jsxs("div", { className: css.nameRow, children: [!grouped && _jsx("strong", { ...(senderTitle === undefined ? {} : { title: senderTitle }), children: senderName }), occurredAt !== undefined && _jsx("span", { className: css.messageTime, children: formatMessageTime(occurredAt) })] })), clampable ? _jsx("div", { className: clamped ? css.messageClamp : undefined, children: bodyNode }) : bodyNode, clampable && (_jsx("button", { type: "button", className: css.messageExpand, "data-message-expand": "true", "aria-expanded": expanded, onClick: () => { setExpanded(value => !value); }, children: expanded ? (t?.('collapseMessage') ?? 'Show less') : (t?.('expandMessage') ?? 'Show more') })), (plan.fallbackNames.length > 0 || plan.fallbackRefs.length > 0) && (_jsxs("div", { className: css.mentionsRow, children: [plan.fallbackNames.map(name => _jsxs("span", { className: css.mention, children: ["@", name] }, name)), plan.fallbackRefs.map(ref => {
|
|
88
89
|
const resolved = cachedResolvedTaskRef(ref);
|
|
@@ -132,17 +133,31 @@ function resolvedTaskRefButton(taskRef, label) {
|
|
|
132
133
|
return button;
|
|
133
134
|
}
|
|
134
135
|
/** Replace a whole-ref code span with its resolved Task chip once known. */
|
|
135
|
-
function renderResolvedMarkdownCodeRef(code, taskLabel) {
|
|
136
|
+
function renderResolvedMarkdownCodeRef(code, taskLabel, refLabel) {
|
|
136
137
|
const segments = splitBrandedRefs((code.textContent ?? '').trim());
|
|
137
138
|
const segment = segments.length === 1 ? segments[0] : undefined;
|
|
138
|
-
const
|
|
139
|
+
const ref = segment?.ref;
|
|
140
|
+
if (ref === undefined)
|
|
141
|
+
return;
|
|
142
|
+
const taskRef = ref.startsWith('task:') === true ? ref : undefined;
|
|
139
143
|
const resolved = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
|
|
140
|
-
if (taskRef
|
|
144
|
+
if (taskRef !== undefined && resolved === undefined)
|
|
145
|
+
return;
|
|
146
|
+
if (taskRef !== undefined) {
|
|
147
|
+
code.replaceWith(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
|
|
141
148
|
return;
|
|
142
|
-
|
|
149
|
+
}
|
|
150
|
+
const button = document.createElement('button');
|
|
151
|
+
button.type = 'button';
|
|
152
|
+
if (css.refLink !== undefined)
|
|
153
|
+
button.className = css.refLink;
|
|
154
|
+
button.dataset.ref = ref;
|
|
155
|
+
button.title = ref;
|
|
156
|
+
button.textContent = refLabel(ref);
|
|
157
|
+
code.replaceWith(button);
|
|
143
158
|
}
|
|
144
159
|
/** Replace resolved Task refs and structured mention handles in one prose text node without changing Markdown structure. */
|
|
145
|
-
function renderResolvedMarkdownText(node, mentionNames, taskLabel) {
|
|
160
|
+
function renderResolvedMarkdownText(node, mentionNames, taskLabel, refLabel) {
|
|
146
161
|
let changed = false;
|
|
147
162
|
const fragment = document.createDocumentFragment();
|
|
148
163
|
for (const refSegment of splitBrandedRefs(node.data)) {
|
|
@@ -163,25 +178,40 @@ function renderResolvedMarkdownText(node, mentionNames, taskLabel) {
|
|
|
163
178
|
}
|
|
164
179
|
const taskRef = refSegment.ref.startsWith('task:') === true ? refSegment.ref : undefined;
|
|
165
180
|
const resolved = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
|
|
166
|
-
|
|
181
|
+
// Task refs need Host resolution before becoming human-readable links;
|
|
182
|
+
// channel/thread refs already have their canonical display text and can be
|
|
183
|
+
// linked immediately at the authored position.
|
|
184
|
+
if (taskRef !== undefined && resolved === undefined) {
|
|
167
185
|
fragment.append(refSegment.text);
|
|
168
186
|
continue;
|
|
169
187
|
}
|
|
170
188
|
changed = true;
|
|
171
|
-
|
|
189
|
+
if (taskRef !== undefined) {
|
|
190
|
+
fragment.append(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
const button = document.createElement('button');
|
|
194
|
+
button.type = 'button';
|
|
195
|
+
if (css.refLink !== undefined)
|
|
196
|
+
button.className = css.refLink;
|
|
197
|
+
button.dataset.ref = refSegment.ref;
|
|
198
|
+
button.title = refSegment.ref;
|
|
199
|
+
button.textContent = refLabel(refSegment.ref);
|
|
200
|
+
fragment.append(button);
|
|
201
|
+
}
|
|
172
202
|
}
|
|
173
203
|
if (changed)
|
|
174
204
|
node.replaceWith(fragment);
|
|
175
205
|
}
|
|
176
206
|
/** Render one literal text run, linkifying branded refs when navigation is available. */
|
|
177
|
-
function renderRefs(text, onOpenRef, taskLabel) {
|
|
207
|
+
function renderRefs(text, onOpenRef, taskLabel, refLabel) {
|
|
178
208
|
if (onOpenRef === undefined)
|
|
179
209
|
return text;
|
|
180
210
|
return splitBrandedRefs(text).map((segment, index) => {
|
|
181
211
|
if (segment.ref === undefined)
|
|
182
212
|
return _jsx(Fragment, { children: segment.text }, index);
|
|
183
213
|
const resolved = cachedResolvedTaskRef(segment.ref);
|
|
184
|
-
const label = resolved !== undefined && segment.ref.startsWith('task:') ? taskLabel(resolved.taskNumber) : segment.ref;
|
|
214
|
+
const label = resolved !== undefined && segment.ref.startsWith('task:') ? taskLabel(resolved.taskNumber) : refLabel(segment.ref);
|
|
185
215
|
return _jsx("button", { type: "button", className: css.refLink, title: segment.ref, onClick: () => { onOpenRef(segment.ref); }, children: label }, index);
|
|
186
216
|
});
|
|
187
217
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type MenuEntry } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
2
2
|
/**
|
|
3
3
|
* Row-level overflow menu shared by sidebar Channel and Agent rows, mirroring
|
|
4
4
|
* the harness session-row pattern: a portal list anchored to a bare ellipsis
|
|
@@ -7,7 +7,8 @@ import type { MenuItem } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
|
7
7
|
export declare function TeamRowMenu({ label, items, onSelect, onOpenChange }: {
|
|
8
8
|
/** Localized action label for the trigger, e.g. "{name} 的操作". */
|
|
9
9
|
readonly label: string;
|
|
10
|
-
|
|
10
|
+
/** Menu rows plus optional non-interactive labels and separators. */
|
|
11
|
+
readonly items: readonly MenuEntry[];
|
|
11
12
|
readonly onSelect: (id: string) => void;
|
|
12
13
|
/** Lets the row pin its hover styling while the portal list is up. */
|
|
13
14
|
readonly onOpenChange?: (open: boolean) => void;
|