@wowyuarm/dsh-agent-team 0.1.0
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/LICENSE +21 -0
- package/README.md +103 -0
- package/README.zh.md +103 -0
- package/cordis.patch.yml +36 -0
- package/package.json +152 -0
- package/packages/agent-team/README.md +58 -0
- package/packages/agent-team/README.zh.md +57 -0
- package/packages/agent-team/lib/index.js +841 -0
- package/packages/agent-team/lib/invariant.js +24 -0
- package/packages/agent-team/lib/ledger.js +2161 -0
- package/packages/agent-team/lib/member-context.js +69 -0
- package/packages/agent-team/lib/preset-roster.js +14 -0
- package/packages/agent-team/lib/spec.js +347 -0
- package/packages/agent-team/lib/typert.host.d.ts +3 -0
- package/packages/agent-team/lib/typert.host.js +2084 -0
- package/packages/agent-team/lib/typert.remote-client.d.ts +54 -0
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -0
- package/packages/agent-team/lib/typert.remote-client.js +1218 -0
- package/packages/agent-team/lib/types/index.d.ts +156 -0
- package/packages/agent-team/lib/types/index.d.ts.map +1 -0
- package/packages/agent-team/lib/types/invariant.d.ts +13 -0
- package/packages/agent-team/lib/types/invariant.d.ts.map +1 -0
- package/packages/agent-team/lib/types/ledger.d.ts +271 -0
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -0
- package/packages/agent-team/lib/types/member-context.d.ts +7 -0
- package/packages/agent-team/lib/types/member-context.d.ts.map +1 -0
- package/packages/agent-team/lib/types/preset-roster.d.ts +5 -0
- package/packages/agent-team/lib/types/preset-roster.d.ts.map +1 -0
- package/packages/agent-team/lib/types/spec.d.ts +13 -0
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -0
- package/packages/agent-team/lib/types/types.d.ts +818 -0
- package/packages/agent-team/lib/types/types.d.ts.map +1 -0
- package/packages/agent-team/lib/types.js +1 -0
- package/packages/agent-team/preset/team-member/agent.cordis.yml +89 -0
- package/packages/client-agent-team/README.md +39 -0
- package/packages/client-agent-team/README.zh.md +39 -0
- package/packages/client-agent-team/lib/client.js +9633 -0
- package/packages/client-agent-team/lib/client.js.map +1 -0
- package/packages/client-agent-team/lib/index.js +5 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +20 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +274 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +19 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +243 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts +21 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +196 -0
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +15 -0
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +144 -0
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +3 -0
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamConversation.js +20 -0
- package/packages/client-agent-team/lib/types/client/TeamFooterAction.d.ts +3 -0
- package/packages/client-agent-team/lib/types/client/TeamFooterAction.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamFooterAction.js +19 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts +13 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamInboxPanel.js +90 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.d.ts +12 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.js +15 -0
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts +5 -0
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +32 -0
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +19 -0
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +24 -0
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +11 -0
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamPresenceDot.js +20 -0
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts +15 -0
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.js +23 -0
- package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts +3 -0
- package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamSettings.js +5 -0
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts +9 -0
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +9 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +22 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +464 -0
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +3 -0
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +39 -0
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.d.ts +11 -0
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.js +6 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts +19 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/index.js +113 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts +224 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/locales.js +222 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts +39 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/navigation.js +96 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +74 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/slots.js +1 -0
- package/packages/client-agent-team/lib/types/client/team-changes.d.ts +28 -0
- package/packages/client-agent-team/lib/types/client/team-changes.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/team-changes.js +70 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +53 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.js +128 -0
- package/packages/client-agent-team/lib/types/client/team-membership.d.ts +25 -0
- package/packages/client-agent-team/lib/types/client/team-membership.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/team-membership.js +39 -0
- package/packages/client-agent-team/lib/types/client/team-separators.d.ts +23 -0
- package/packages/client-agent-team/lib/types/client/team-separators.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/team-separators.js +52 -0
- package/packages/client-agent-team/lib/types/client/timeline-scroll.d.ts +18 -0
- package/packages/client-agent-team/lib/types/client/timeline-scroll.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/timeline-scroll.js +54 -0
- package/packages/client-agent-team/lib/types/index.d.ts +3 -0
- package/packages/client-agent-team/lib/types/index.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/index.js +2 -0
- package/packages/tool-agent-team/README.md +23 -0
- package/packages/tool-agent-team/README.zh.md +23 -0
- package/packages/tool-agent-team/lib/index.js +294 -0
- package/packages/tool-agent-team/lib/types/index.d.ts +5 -0
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Button, IconEditOutline16, IconPlusOutline16, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
|
+
import { TeamPresenceDot } from "./TeamPresenceDot.js";
|
|
5
|
+
import { TeamRowMenu } from "./TeamRowMenu.js";
|
|
6
|
+
import { TeamSidebarSection } from "./TeamSidebarSection.js";
|
|
7
|
+
import { useChannelMembership } from "./team-membership.js";
|
|
8
|
+
import createCss from './create.module.css';
|
|
9
|
+
import css from './sidebar.module.css';
|
|
10
|
+
export function TeamChannelsPanel(props) {
|
|
11
|
+
const { workspaceId, loadMembers, loadChannels, subscribeChanges, createChannel, updateChannel, creatingAgents, selectedChannelRef, selectChannel, t } = props;
|
|
12
|
+
const [view, setView] = useState();
|
|
13
|
+
const [members, setMembers] = useState([]);
|
|
14
|
+
const [loading, setLoading] = useState(true);
|
|
15
|
+
const [error, setError] = useState();
|
|
16
|
+
const [formOpen, setFormOpen] = useState(false);
|
|
17
|
+
const [name, setName] = useState('');
|
|
18
|
+
const [description, setDescription] = useState('');
|
|
19
|
+
const [selected, setSelected] = useState(new Set());
|
|
20
|
+
const [mutating, setMutating] = useState(false);
|
|
21
|
+
const [pendingCreate, setPendingCreate] = useState();
|
|
22
|
+
const previousCreatingKey = useRef(creatingAgents.map(request => request.requestId).join(','));
|
|
23
|
+
const triggerRef = useRef(null);
|
|
24
|
+
const refresh = useCallback(async () => {
|
|
25
|
+
setLoading(true);
|
|
26
|
+
const [channelResult, memberResult] = await Promise.all([
|
|
27
|
+
loadChannels({ workspaceId, limit: 1 }),
|
|
28
|
+
loadMembers({ workspaceId }),
|
|
29
|
+
]);
|
|
30
|
+
if (channelResult.ok && memberResult.ok) {
|
|
31
|
+
setView(channelResult.value);
|
|
32
|
+
const visibleMembers = memberResult.value.filter(status => status.member.state !== 'inactive');
|
|
33
|
+
setMembers(visibleMembers);
|
|
34
|
+
const selectable = new Set(visibleMembers.filter(status => status.presence !== 'unavailable')
|
|
35
|
+
.map(status => status.member.memberId));
|
|
36
|
+
setSelected(current => new Set([...current].filter(memberId => selectable.has(memberId))));
|
|
37
|
+
setError(undefined);
|
|
38
|
+
}
|
|
39
|
+
else if (!channelResult.ok) {
|
|
40
|
+
setError(channelResult.error.message);
|
|
41
|
+
}
|
|
42
|
+
else if (!memberResult.ok) {
|
|
43
|
+
setError(memberResult.error.message);
|
|
44
|
+
}
|
|
45
|
+
setLoading(false);
|
|
46
|
+
}, [loadChannels, loadMembers, workspaceId]);
|
|
47
|
+
useEffect(() => { void refresh(); }, [refresh]);
|
|
48
|
+
useEffect(() => subscribeChanges({ kind: 'workspace', workspaceId }, update => {
|
|
49
|
+
if (update.type === 'failed') {
|
|
50
|
+
setError(update.message);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
void refresh();
|
|
54
|
+
}), [subscribeChanges, refresh, workspaceId]);
|
|
55
|
+
const creatingKey = creatingAgents.map(request => request.requestId).join(',');
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (previousCreatingKey.current === creatingKey)
|
|
58
|
+
return;
|
|
59
|
+
previousCreatingKey.current = creatingKey;
|
|
60
|
+
void refresh();
|
|
61
|
+
}, [creatingKey, refresh]);
|
|
62
|
+
const membership = useMemo(() => {
|
|
63
|
+
const byChannel = new Map();
|
|
64
|
+
for (const item of view?.members ?? []) {
|
|
65
|
+
const ids = byChannel.get(item.channelRef) ?? new Set();
|
|
66
|
+
ids.add(item.memberId);
|
|
67
|
+
byChannel.set(item.channelRef, ids);
|
|
68
|
+
}
|
|
69
|
+
return byChannel;
|
|
70
|
+
}, [view]);
|
|
71
|
+
const closeForm = () => {
|
|
72
|
+
if (mutating)
|
|
73
|
+
return;
|
|
74
|
+
setFormOpen(false);
|
|
75
|
+
queueMicrotask(() => { triggerRef.current?.focus(); });
|
|
76
|
+
};
|
|
77
|
+
const submit = async (event) => {
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
if (mutating || name.trim() === '' || description.trim() === '')
|
|
80
|
+
return;
|
|
81
|
+
const payload = { workspaceId, name: name.trim(), description: description.trim(), memberIds: [...selected] };
|
|
82
|
+
const samePending = pendingCreate !== undefined && pendingCreate.workspaceId === payload.workspaceId
|
|
83
|
+
&& pendingCreate.name === payload.name && pendingCreate.description === payload.description
|
|
84
|
+
&& JSON.stringify(pendingCreate.memberIds) === JSON.stringify(payload.memberIds);
|
|
85
|
+
const request = samePending ? pendingCreate : {
|
|
86
|
+
requestId: crypto.randomUUID(), ...payload,
|
|
87
|
+
};
|
|
88
|
+
setPendingCreate(request);
|
|
89
|
+
setMutating(true);
|
|
90
|
+
setError(undefined);
|
|
91
|
+
try {
|
|
92
|
+
const result = await createChannel(request);
|
|
93
|
+
if (result.ok) {
|
|
94
|
+
setPendingCreate(undefined);
|
|
95
|
+
setName('');
|
|
96
|
+
setDescription('');
|
|
97
|
+
setSelected(new Set());
|
|
98
|
+
setFormOpen(false);
|
|
99
|
+
await refresh();
|
|
100
|
+
queueMicrotask(() => { triggerRef.current?.focus(); });
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
setError(result.error.message);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (cause) {
|
|
107
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
setMutating(false);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
return (_jsxs("div", { className: css.panel, children: [_jsx(Modal, { open: formOpen, onClose: closeForm, title: t('addChannel'), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: mutating, onClick: closeForm, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-channel-create-form", variant: "primary", disabled: mutating || name.trim() === '' || description.trim() === '', children: mutating ? t('creatingChannel') : t('createChannel') })] }), children: _jsxs("form", { id: "team-channel-create-form", className: createCss.form, onSubmit: event => { void submit(event); }, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelName') }), _jsx(Input, { className: createCss.input, value: name, disabled: mutating, autoFocus: true, onChange: event => { setName(event.target.value); setPendingCreate(undefined); } })] }), _jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelDescription') }), _jsx(Input, { className: createCss.input, value: description, disabled: mutating, onChange: event => { setDescription(event.target.value); setPendingCreate(undefined); } })] }), _jsxs("fieldset", { className: createCss.memberPicker, disabled: mutating, children: [_jsx("legend", { children: t('initialMembers') }), creatingAgents.map(request => (_jsxs("label", { className: createCss.memberOption, children: [_jsx("input", { type: "checkbox", disabled: true, "aria-describedby": `creating-member-reason-${request.requestId}` }), _jsx("span", { className: createCss.unavailableDot, "aria-hidden": "true" }), _jsx("span", { children: request.handle }), _jsx("small", { id: `creating-member-reason-${request.requestId}`, children: t('memberCreatingReason') })] }, request.requestId))), members.map(status => {
|
|
114
|
+
const disabled = status.presence === 'unavailable';
|
|
115
|
+
const reasonId = `initial-member-reason-${status.member.memberId}`;
|
|
116
|
+
return (_jsxs("label", { className: createCss.memberOption, children: [_jsx("input", { type: "checkbox", disabled: disabled, "aria-describedby": disabled ? reasonId : undefined, checked: selected.has(status.member.memberId), onChange: event => {
|
|
117
|
+
setSelected(current => {
|
|
118
|
+
const next = new Set(current);
|
|
119
|
+
if (event.target.checked)
|
|
120
|
+
next.add(status.member.memberId);
|
|
121
|
+
else
|
|
122
|
+
next.delete(status.member.memberId);
|
|
123
|
+
return next;
|
|
124
|
+
});
|
|
125
|
+
setPendingCreate(undefined);
|
|
126
|
+
} }), _jsx(TeamPresenceDot, { status: status, t: t }), _jsx("span", { children: status.member.handle }), disabled && _jsx("small", { id: reasonId, children: t('memberUnavailableReason') })] }, status.member.memberId));
|
|
127
|
+
})] }), 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: view?.channels.map(channel => {
|
|
128
|
+
const joined = membership.get(channel.channelRef) ?? new Set();
|
|
129
|
+
return (_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));
|
|
130
|
+
}) })] }), !formOpen && error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] }));
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* One sidebar Channel row: the select button keeps the `#` identity while the
|
|
134
|
+
* row menu carries the entry point; editing covers display facts and membership.
|
|
135
|
+
*/
|
|
136
|
+
function ChannelRow({ channel, members, joinedIds, selected, updateChannel, joinChannel, removeChannelMember, onCommitted, selectChannel, t }) {
|
|
137
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
138
|
+
const [editing, setEditing] = useState(false);
|
|
139
|
+
return (_jsxs(_Fragment, { children: [_jsxs("article", { className: css.channelRow, "data-menu-open": menuOpen || undefined, "aria-current": selected ? 'page' : undefined, children: [_jsx("button", { type: "button", className: css.channelSelect, "aria-label": `# ${channel.name}`, onClick: () => { selectChannel(channel.channelRef); }, children: _jsxs("strong", { className: css.channelName, children: ["# ", channel.name] }) }), _jsx("span", { className: css.rowMenu, children: _jsx(TeamRowMenu, { label: t('actionsChannel', { name: channel.name }), items: [{ id: 'edit', label: t('editChannel'), icon: _jsx(IconEditOutline16, {}) }], onSelect: () => { setEditing(true); }, onOpenChange: setMenuOpen }) })] }), editing && (_jsx(ChannelEditorDialog, { channel: channel, members: members, joinedIds: joinedIds, updateChannel: updateChannel, joinChannel: joinChannel, removeChannelMember: removeChannelMember, onCommitted: onCommitted, onClose: () => { setEditing(false); }, t: t }))] }));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Channel editor: name and description commit through one durable update;
|
|
143
|
+
* Channel membership below keeps its own immediate add/remove flow.
|
|
144
|
+
*/
|
|
145
|
+
function ChannelEditorDialog({ channel, members, joinedIds, updateChannel, joinChannel, removeChannelMember, onCommitted, onClose, t }) {
|
|
146
|
+
const [name, setName] = useState(channel.name);
|
|
147
|
+
const [description, setDescription] = useState(channel.description);
|
|
148
|
+
const [saving, setSaving] = useState(false);
|
|
149
|
+
const [error, setError] = useState();
|
|
150
|
+
const pendingRequest = useRef();
|
|
151
|
+
const membership = useChannelMembership({ joinChannel, removeChannelMember }, change => change.memberId, async () => { await onCommitted(); });
|
|
152
|
+
const dirty = name.trim() !== channel.name || description.trim() !== channel.description;
|
|
153
|
+
const submit = async (event) => {
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
const normalizedName = name.trim();
|
|
156
|
+
const normalizedDescription = description.trim();
|
|
157
|
+
if (saving || !dirty || normalizedName === '' || normalizedDescription === '')
|
|
158
|
+
return;
|
|
159
|
+
const samePending = pendingRequest.current !== undefined && pendingRequest.current.channelRef === channel.channelRef
|
|
160
|
+
&& pendingRequest.current.name === normalizedName && pendingRequest.current.description === normalizedDescription;
|
|
161
|
+
const request = samePending ? pendingRequest.current : {
|
|
162
|
+
requestId: crypto.randomUUID(),
|
|
163
|
+
workspaceId: channel.workspaceId,
|
|
164
|
+
channelRef: channel.channelRef,
|
|
165
|
+
name: normalizedName,
|
|
166
|
+
description: normalizedDescription,
|
|
167
|
+
};
|
|
168
|
+
pendingRequest.current = request;
|
|
169
|
+
setSaving(true);
|
|
170
|
+
setError(undefined);
|
|
171
|
+
try {
|
|
172
|
+
const result = await updateChannel(request);
|
|
173
|
+
if (result.ok) {
|
|
174
|
+
pendingRequest.current = undefined;
|
|
175
|
+
await onCommitted();
|
|
176
|
+
onClose();
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
setError(result.error.message);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch (cause) {
|
|
183
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
setSaving(false);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
return (_jsx(Modal, { open: true, onClose: onClose, title: t('editChannel'), description: `# ${channel.name}`, closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: saving, onClick: onClose, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-channel-edit-form", variant: "primary", disabled: saving || !dirty || name.trim() === '' || description.trim() === '', children: saving ? t('editSaving') : t('editSave') })] }), children: _jsxs("form", { id: "team-channel-edit-form", className: createCss.form, onSubmit: event => { void submit(event); }, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelName') }), _jsx(Input, { className: createCss.input, value: name, onChange: event => { setName(event.target.value); pendingRequest.current = undefined; }, disabled: saving, autoFocus: true })] }), _jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelDescription') }), _jsx(Input, { className: createCss.input, value: description, onChange: event => { setDescription(event.target.value); pendingRequest.current = undefined; }, disabled: saving })] }), _jsxs("fieldset", { className: createCss.memberPicker, disabled: saving, children: [_jsx("legend", { children: t('channelMembersSection') }), members.map(status => {
|
|
190
|
+
const joined = joinedIds.has(status.member.memberId);
|
|
191
|
+
const rowPending = membership.pending.has(status.member.memberId);
|
|
192
|
+
const disabled = rowPending || (!joined && status.presence === 'unavailable');
|
|
193
|
+
const rowError = membership.errors.get(status.member.memberId);
|
|
194
|
+
return _jsxs("div", { className: css.editMemberRow, children: [_jsx(TeamPresenceDot, { status: status, t: t }), _jsxs("span", { className: css.editMemberCopy, children: [_jsxs("strong", { children: ["@", status.member.handle] }), _jsx("small", { children: status.member.description })] }), _jsx(Button, { size: "sm", variant: "outline", disabled: disabled, onClick: () => { void membership.change({ workspaceId: channel.workspaceId, channelRef: channel.channelRef, memberId: status.member.memberId, joined }); }, children: rowPending ? t('membershipUpdating') : joined ? t('removeFromChannel') : t('addToChannel') }), rowError !== undefined && _jsx("p", { className: css.rowError, role: "alert", children: rowError })] }, status.member.memberId);
|
|
195
|
+
})] }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }));
|
|
196
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AgentTeamClientMemberStatus, AgentTeamMemberId } from '@wowyuarm/dsh-agent-team/types';
|
|
2
|
+
import type { TeamConversationProps } from './slots.ts';
|
|
3
|
+
export declare function TeamComposer({ members, recipients, draft, pending, confirmation, error, onDraftChange, onRecipientsChange, onSubmit, t }: {
|
|
4
|
+
readonly members: readonly AgentTeamClientMemberStatus[];
|
|
5
|
+
readonly recipients: ReadonlySet<AgentTeamMemberId>;
|
|
6
|
+
readonly draft: string;
|
|
7
|
+
readonly pending: boolean;
|
|
8
|
+
readonly confirmation?: string;
|
|
9
|
+
readonly error?: string;
|
|
10
|
+
readonly onDraftChange: (draft: string) => void;
|
|
11
|
+
readonly onRecipientsChange: (recipients: ReadonlySet<AgentTeamMemberId>) => void;
|
|
12
|
+
readonly onSubmit: () => void;
|
|
13
|
+
readonly t: TeamConversationProps['t'];
|
|
14
|
+
}): import("react").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=TeamComposer.d.ts.map
|
|
@@ -0,0 +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;AAoCvD,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE;IACzI,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,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC,+BAkKA"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
+
import { IconSendOutline16, useAnchoredMaxHeight, useDismissOnOutsidePointer } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
|
+
import { TeamPresenceDot } from "./TeamPresenceDot.js";
|
|
5
|
+
import css from './composer.module.css';
|
|
6
|
+
function findMention(draft, caret) {
|
|
7
|
+
const beforeCaret = draft.slice(0, caret);
|
|
8
|
+
if (beforeCaret.length === 0 || /\s/u.test(beforeCaret.at(-1) ?? ''))
|
|
9
|
+
return undefined;
|
|
10
|
+
let tokenStart = beforeCaret.length;
|
|
11
|
+
while (tokenStart > 0 && !/\s/u.test(beforeCaret[tokenStart - 1]))
|
|
12
|
+
tokenStart -= 1;
|
|
13
|
+
const at = beforeCaret.lastIndexOf('@');
|
|
14
|
+
if (at < tokenStart)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (at > 0 && /[\p{L}\p{N}_]/u.test(beforeCaret[at - 1]))
|
|
17
|
+
return undefined;
|
|
18
|
+
return { start: at, end: caret, query: beforeCaret.slice(at + 1) };
|
|
19
|
+
}
|
|
20
|
+
function escapeRegExp(value) {
|
|
21
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
22
|
+
}
|
|
23
|
+
function containsMention(draft, handle) {
|
|
24
|
+
return new RegExp(`(?:^|[^\\p{L}\\p{N}_])@${escapeRegExp(handle)}(?=$|[^\\p{L}\\p{N}_])`, 'u').test(draft);
|
|
25
|
+
}
|
|
26
|
+
function mentionCandidates(members, query) {
|
|
27
|
+
const normalized = query.toLocaleLowerCase();
|
|
28
|
+
return members.filter(status => status.presence !== 'unavailable'
|
|
29
|
+
&& status.member.state !== 'inactive'
|
|
30
|
+
&& status.member.handle.toLocaleLowerCase().startsWith(normalized));
|
|
31
|
+
}
|
|
32
|
+
export function TeamComposer({ members, recipients, draft, pending, confirmation, error, onDraftChange, onRecipientsChange, onSubmit, t }) {
|
|
33
|
+
const inputRef = useRef(null);
|
|
34
|
+
const rootRef = useRef(null);
|
|
35
|
+
const menuRef = useRef(null);
|
|
36
|
+
const composingRef = useRef(false);
|
|
37
|
+
const [mention, setMention] = useState();
|
|
38
|
+
const [highlight, setHighlight] = useState(0);
|
|
39
|
+
const candidates = mention === undefined ? [] : mentionCandidates(members, mention.query);
|
|
40
|
+
const menuOpen = mention !== undefined && candidates.length > 0;
|
|
41
|
+
const activeCandidate = candidates[highlight];
|
|
42
|
+
const listId = 'team-mention-suggestions';
|
|
43
|
+
const menuMaxHeight = useAnchoredMaxHeight(menuRef, 320, menuOpen ? draft : null);
|
|
44
|
+
useDismissOnOutsidePointer(rootRef, menuOpen, open => { if (!open)
|
|
45
|
+
setMention(undefined); });
|
|
46
|
+
useLayoutEffect(() => {
|
|
47
|
+
const input = inputRef.current;
|
|
48
|
+
if (input === null)
|
|
49
|
+
return;
|
|
50
|
+
input.style.height = 'auto';
|
|
51
|
+
input.style.height = `${Math.min(input.scrollHeight, 180)}px`;
|
|
52
|
+
}, [draft]);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (mention === undefined || candidates.length === 0) {
|
|
55
|
+
setHighlight(0);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
setHighlight(current => Math.min(current, candidates.length - 1));
|
|
59
|
+
}, [mention, candidates.length]);
|
|
60
|
+
const pruneRecipients = (nextDraft) => {
|
|
61
|
+
const knownMembers = new Map(members.map(status => [status.member.memberId, status.member]));
|
|
62
|
+
const next = new Set([...recipients].filter(memberId => {
|
|
63
|
+
const member = knownMembers.get(memberId);
|
|
64
|
+
return member !== undefined && containsMention(nextDraft, member.handle);
|
|
65
|
+
}));
|
|
66
|
+
if (next.size !== recipients.size)
|
|
67
|
+
onRecipientsChange(next);
|
|
68
|
+
};
|
|
69
|
+
const updateMention = (nextDraft, caret) => {
|
|
70
|
+
const match = findMention(nextDraft, caret);
|
|
71
|
+
setMention(match);
|
|
72
|
+
if (match === undefined)
|
|
73
|
+
setHighlight(0);
|
|
74
|
+
};
|
|
75
|
+
const onChange = (event) => {
|
|
76
|
+
const nextDraft = event.target.value;
|
|
77
|
+
onDraftChange(nextDraft);
|
|
78
|
+
pruneRecipients(nextDraft);
|
|
79
|
+
updateMention(nextDraft, event.target.selectionStart ?? nextDraft.length);
|
|
80
|
+
};
|
|
81
|
+
const selectMention = (member) => {
|
|
82
|
+
if (mention === undefined)
|
|
83
|
+
return;
|
|
84
|
+
const inserted = `@${member.member.handle} `;
|
|
85
|
+
const nextDraft = `${draft.slice(0, mention.start)}${inserted}${draft.slice(mention.end)}`;
|
|
86
|
+
const nextCaret = mention.start + inserted.length;
|
|
87
|
+
const nextRecipients = new Set(recipients).add(member.member.memberId);
|
|
88
|
+
onDraftChange(nextDraft);
|
|
89
|
+
onRecipientsChange(nextRecipients);
|
|
90
|
+
setMention(undefined);
|
|
91
|
+
setHighlight(0);
|
|
92
|
+
requestAnimationFrame(() => {
|
|
93
|
+
const input = inputRef.current;
|
|
94
|
+
if (input === null)
|
|
95
|
+
return;
|
|
96
|
+
input.focus({ preventScroll: true });
|
|
97
|
+
input.setSelectionRange(nextCaret, nextCaret);
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
const onKeyDown = (event) => {
|
|
101
|
+
const composing = composingRef.current || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;
|
|
102
|
+
if (menuOpen && event.key === 'ArrowDown') {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
setHighlight(current => (current + 1) % candidates.length);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (menuOpen && event.key === 'ArrowUp') {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
setHighlight(current => (current - 1 + candidates.length) % candidates.length);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (event.key === 'Escape' && menuOpen) {
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
setMention(undefined);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Tab accepts the highlighted candidate; Shift+Tab keeps default focus reversal.
|
|
118
|
+
if (menuOpen && event.key === 'Tab' && !event.shiftKey) {
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
if (activeCandidate !== undefined)
|
|
121
|
+
selectMention(activeCandidate);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (event.key !== 'Enter' || event.shiftKey || composing || event.repeat)
|
|
125
|
+
return;
|
|
126
|
+
if (menuOpen && activeCandidate !== undefined) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
selectMention(activeCandidate);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
if (!pending && draft.trim() !== '')
|
|
133
|
+
onSubmit();
|
|
134
|
+
};
|
|
135
|
+
return _jsxs("form", { ref: rootRef, className: css.root, onSubmit: event => {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
if (!pending && draft.trim() !== '')
|
|
138
|
+
onSubmit();
|
|
139
|
+
}, 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: candidates.map((status, index) => {
|
|
140
|
+
const optionId = `${listId}-${status.member.memberId}`;
|
|
141
|
+
const selected = index === highlight;
|
|
142
|
+
return _jsxs("button", { id: optionId, type: "button", role: "option", "aria-selected": selected, className: css.mentionOption, onMouseDown: event => { event.preventDefault(); }, onClick: () => { selectMention(status); }, children: [_jsx(TeamPresenceDot, { status: status, t: t }), _jsxs("span", { className: css.mentionName, children: ["@", status.member.handle] }), _jsx("span", { className: css.mentionDescription, children: status.member.description })] }, status.member.memberId);
|
|
143
|
+
}) }), _jsx("textarea", { ref: inputRef, "aria-label": t('messageDraft'), "aria-autocomplete": "list", "aria-controls": menuOpen ? listId : undefined, "aria-activedescendant": menuOpen && activeCandidate !== undefined ? `${listId}-${activeCandidate.member.memberId}` : undefined, "aria-expanded": menuOpen, value: draft, disabled: pending, placeholder: t('messagePlaceholder'), rows: 1, onChange: onChange, 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(', ') }) }), _jsx("div", { className: css.toolbar, children: _jsx("button", { type: "submit", className: css.sendButton, "aria-label": pending ? t('sendingMessage') : t('sendMessage'), disabled: pending || draft.trim() === '', children: _jsx(IconSendOutline16, { size: 16 }) }) })] }), error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] });
|
|
144
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { TeamConversationProps } from './slots.ts';
|
|
2
|
+
export declare function TeamConversation({ t, useWorkspaces, navigation, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, reply, changeTask, selectThread, backToWorkspace, backToChannels }: TeamConversationProps): import("react").JSX.Element;
|
|
3
|
+
//# sourceMappingURL=TeamConversation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TeamConversation.d.ts","sourceRoot":"","sources":["../../../src/client/TeamConversation.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAKvD,wBAAgB,gBAAgB,CAAC,EAAE,CAAC,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,EAAE,qBAAqB,+BAoBpR"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useSyncExternalStore } from 'react';
|
|
3
|
+
import { TeamChannelPage } from "./TeamChannelPage.js";
|
|
4
|
+
import { TeamThreadPage } from "./TeamThreadPage.js";
|
|
5
|
+
import css from './conversation.module.css';
|
|
6
|
+
export function TeamConversation({ t, useWorkspaces, navigation, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, reply, changeTask, selectThread, backToWorkspace, backToChannels }) {
|
|
7
|
+
const navigationState = useSyncExternalStore(navigation.subscribe, navigation.getSnapshot, navigation.getSnapshot);
|
|
8
|
+
const workspaces = useWorkspaces(state => state.items);
|
|
9
|
+
const current = workspaces.find(workspace => workspace.workspaceId === navigationState.workspaceId);
|
|
10
|
+
if (current !== undefined && navigationState.taskRef !== undefined && navigationState.threadRef !== undefined) {
|
|
11
|
+
return _jsx(TeamThreadPage, { workspaceId: current.workspaceId, taskRef: navigationState.taskRef, threadRef: navigationState.threadRef, backToWorkspace: backToWorkspace, ...(navigationState.channelRef === undefined ? {} : { channelRef: navigationState.channelRef }), ...(navigationState.taskNumber === undefined ? {} : { taskNumber: navigationState.taskNumber }), readThread: readThread, loadChannels: loadChannels, loadThreadHistory: loadThreadHistory, subscribeChanges: subscribeChanges, loadMembers: loadMembers, reply: reply, changeTask: changeTask, t: t }, navigationState.threadRef);
|
|
12
|
+
}
|
|
13
|
+
if (current !== undefined && navigationState.channelRef !== undefined) {
|
|
14
|
+
return _jsx(TeamChannelPage, { workspaceId: current.workspaceId, channelRef: navigationState.channelRef, loadChannels: loadChannels, subscribeChanges: subscribeChanges, loadMembers: loadMembers, sendMessage: sendMessage, joinChannel: joinChannel, removeChannelMember: removeChannelMember, selectThread: selectThread, backToChannels: backToChannels, t: t }, navigationState.channelRef);
|
|
15
|
+
}
|
|
16
|
+
const welcome = current === undefined
|
|
17
|
+
? { eyebrow: t('teamMode'), title: t('team'), body: t('empty') }
|
|
18
|
+
: { eyebrow: current.title, title: t('channels'), body: t('selectChannelHint') };
|
|
19
|
+
return _jsx("main", { className: css.welcomeSurface, "data-team-conversation": true, children: _jsxs("div", { className: css.welcome, children: [_jsx("span", { className: css.welcomeEyebrow, children: welcome.eyebrow }), _jsx("h1", { children: welcome.title }), _jsx("p", { children: welcome.body })] }) });
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TeamFooterAction.d.ts","sourceRoot":"","sources":["../../../src/client/TeamFooterAction.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAGjD,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,eAAe,+BAgC9F"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useSyncExternalStore } from 'react';
|
|
3
|
+
import { IconAgentPresetOutline16, IconChevronLeftOutline14, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
|
+
import css from './team.module.css';
|
|
5
|
+
export function TeamFooterAction({ wide, navigation, enterTeam, leaveTeam, t }) {
|
|
6
|
+
const state = useSyncExternalStore(navigation.subscribe, navigation.getSnapshot, navigation.getSnapshot);
|
|
7
|
+
const inTeam = state.mode === 'team';
|
|
8
|
+
const label = inTeam ? t('backToConversations') : t('team');
|
|
9
|
+
useLayoutEffect(() => {
|
|
10
|
+
if (typeof document === 'undefined')
|
|
11
|
+
return;
|
|
12
|
+
if (inTeam) {
|
|
13
|
+
document.documentElement.dataset.agentTeamMode = 'team';
|
|
14
|
+
return () => { delete document.documentElement.dataset.agentTeamMode; };
|
|
15
|
+
}
|
|
16
|
+
delete document.documentElement.dataset.agentTeamMode;
|
|
17
|
+
}, [inTeam]);
|
|
18
|
+
return (_jsx(_Fragment, { children: _jsx("div", { className: wide ? css.footerStack : `${css.footerStack} ${css.railStack}`, children: _jsx(Tooltip, { label: label, delayMs: 500, disabled: wide, children: _jsxs("button", { type: "button", className: wide ? css.footerAction : `${css.footerAction} ${css.rail}`, "aria-label": label, "data-team-action": inTeam ? 'leave' : 'enter', onClick: inTeam ? leaveTeam : enterTeam, children: [inTeam ? _jsx(IconChevronLeftOutline14, { size: wide ? 16 : 18 }) : _jsx(IconAgentPresetOutline16, { size: wide ? 16 : 18 }), wide && _jsx("span", { children: label })] }) }) }) }));
|
|
19
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client';
|
|
2
|
+
import type { TeamConversationProps } from './slots.ts';
|
|
3
|
+
interface TeamInboxPanelProps {
|
|
4
|
+
readonly workspaceId: WorkspaceId;
|
|
5
|
+
readonly loadInbox: TeamConversationProps['loadInbox'];
|
|
6
|
+
readonly loadChannels: TeamConversationProps['loadChannels'];
|
|
7
|
+
readonly loadChanges: TeamConversationProps['loadChanges'];
|
|
8
|
+
readonly selectThread: TeamConversationProps['selectThread'];
|
|
9
|
+
readonly t: TeamConversationProps['t'];
|
|
10
|
+
}
|
|
11
|
+
export declare function TeamInboxPanel({ workspaceId, loadInbox, loadChannels, loadChanges, selectThread, t }: TeamInboxPanelProps): import("react").JSX.Element;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=TeamInboxPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TeamInboxPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamInboxPanel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAIvD,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC,WAAW,CAAC,CAAA;IACtD,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AAED,wBAAgB,cAAc,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC,EAAE,EAAE,mBAAmB,+BAkGzH"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Button } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
|
+
import { formatTaskStatus } from "./team-formatters.js";
|
|
5
|
+
import css from './inbox.module.css';
|
|
6
|
+
export function TeamInboxPanel({ workspaceId, loadInbox, loadChannels, loadChanges, selectThread, t }) {
|
|
7
|
+
const [inbox, setInbox] = useState();
|
|
8
|
+
const [view, setView] = useState();
|
|
9
|
+
const [error, setError] = useState();
|
|
10
|
+
const [loading, setLoading] = useState(true);
|
|
11
|
+
const active = useRef(false);
|
|
12
|
+
const refreshSequence = useRef(0);
|
|
13
|
+
const refresh = async (clearError = false) => {
|
|
14
|
+
const sequence = refreshSequence.current + 1;
|
|
15
|
+
refreshSequence.current = sequence;
|
|
16
|
+
if (clearError)
|
|
17
|
+
setError(undefined);
|
|
18
|
+
try {
|
|
19
|
+
const [nextInbox, nextView] = await Promise.all([
|
|
20
|
+
loadInbox({ workspaceId }),
|
|
21
|
+
loadChannels({ workspaceId, topLevelOnly: true, includeActivities: false, limit: 100 }),
|
|
22
|
+
]);
|
|
23
|
+
if (!active.current || sequence !== refreshSequence.current)
|
|
24
|
+
return;
|
|
25
|
+
if (!nextInbox.ok) {
|
|
26
|
+
setError(nextInbox.error.message);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!nextView.ok) {
|
|
30
|
+
setError(nextView.error.message);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
setInbox(nextInbox.value);
|
|
34
|
+
setView(nextView.value);
|
|
35
|
+
setError(undefined);
|
|
36
|
+
}
|
|
37
|
+
catch (cause) {
|
|
38
|
+
if (active.current && sequence === refreshSequence.current)
|
|
39
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
if (active.current && sequence === refreshSequence.current)
|
|
43
|
+
setLoading(false);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
let listening = true;
|
|
48
|
+
active.current = true;
|
|
49
|
+
setInbox(undefined);
|
|
50
|
+
setView(undefined);
|
|
51
|
+
setError(undefined);
|
|
52
|
+
setLoading(true);
|
|
53
|
+
void refresh();
|
|
54
|
+
void (async () => {
|
|
55
|
+
let version = 0;
|
|
56
|
+
while (listening) {
|
|
57
|
+
try {
|
|
58
|
+
const changed = await loadChanges({ afterVersion: version });
|
|
59
|
+
if (!listening)
|
|
60
|
+
return;
|
|
61
|
+
if (!changed.ok) {
|
|
62
|
+
setError(changed.error.message);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (changed.value.version > version) {
|
|
66
|
+
version = changed.value.version;
|
|
67
|
+
await refresh();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (cause) {
|
|
71
|
+
if (listening)
|
|
72
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
})();
|
|
77
|
+
return () => {
|
|
78
|
+
listening = false;
|
|
79
|
+
active.current = false;
|
|
80
|
+
refreshSequence.current += 1;
|
|
81
|
+
};
|
|
82
|
+
}, [workspaceId]);
|
|
83
|
+
const channels = useMemo(() => new Map(view?.channels.map(channel => [channel.channelRef, channel]) ?? []), [view]);
|
|
84
|
+
const taskNumbers = useMemo(() => new Map(view?.taskNumbers.map(item => [item.taskRef, item.taskNumber]) ?? []), [view]);
|
|
85
|
+
return _jsxs("main", { className: css.surface, "data-team-inbox": true, children: [_jsxs("header", { className: css.header, children: [_jsxs("div", { children: [_jsx("h1", { children: t('inbox') }), inbox !== undefined && _jsx("p", { children: inbox.totalUnreadCount === 0 ? t('inboxEmpty') : t('inboxUnreadCount', { count: inbox.totalUnreadCount }) })] }), inbox !== undefined && inbox.totalDirectCount > 0 && _jsx("span", { className: css.directTotal, children: t('inboxDirectCount', { count: inbox.totalDirectCount }) })] }), _jsxs("section", { className: css.list, "aria-label": t('inbox'), children: [loading && inbox === undefined && error === undefined && _jsx("p", { className: css.state, children: t('loadingInbox') }), !loading && inbox?.items.length === 0 && error === undefined && _jsx("p", { className: css.state, children: t('inboxEmpty') }), error !== undefined && _jsxs("div", { className: css.error, role: "alert", children: [_jsx("span", { children: error }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => { void refresh(true); }, children: t('retry') })] }), inbox?.items.map(item => {
|
|
86
|
+
const channel = channels.get(item.channelRef);
|
|
87
|
+
const taskNumber = taskNumbers.get(item.task.taskRef);
|
|
88
|
+
return _jsxs("button", { type: "button", className: css.item, onClick: () => { selectThread(item.task.taskRef, item.thread.threadRef, item.channelRef, taskNumber); }, children: [_jsx("span", { className: css.priority, "data-direct": item.directCount > 0, "aria-hidden": "true" }), _jsxs("span", { className: css.copy, children: [_jsx("strong", { children: taskNumber === undefined ? t('task') : `Task #${taskNumber}` }), _jsxs("small", { children: [channel === undefined ? t('channelUnknown') : `# ${channel.name}`, " \u00B7 ", formatTaskStatus(item.task.status, t)] })] }), _jsxs("span", { className: css.count, "aria-label": `${t('inboxUnreadCount', { count: item.unreadCount })}${item.directCount > 0 ? `, ${t('inboxDirectCount', { count: item.directCount })}` : ''}`, children: [t('inboxUnreadCount', { count: item.unreadCount }), item.directCount > 0 && _jsxs(_Fragment, { children: [" \u00B7 ", t('inboxDirectCount', { count: item.directCount })] })] })] }, item.thread.threadRef);
|
|
89
|
+
})] })] });
|
|
90
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentTeamClientMemberStatus } from '@wowyuarm/dsh-agent-team/types';
|
|
2
|
+
import type { TeamSidebarProps } from './slots.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Sidebar Member avatar reusing the conversation identity language: the
|
|
5
|
+
* deterministic member hue and handle initial, with the presence indicator
|
|
6
|
+
* overlaid at the bottom-right so one glyph carries identity and state.
|
|
7
|
+
*/
|
|
8
|
+
export declare function TeamMemberAvatar({ status, t }: {
|
|
9
|
+
readonly status: AgentTeamClientMemberStatus;
|
|
10
|
+
readonly t: TeamSidebarProps['t'];
|
|
11
|
+
}): import("react").JSX.Element;
|
|
12
|
+
//# sourceMappingURL=TeamMemberAvatar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TeamMemberAvatar.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMemberAvatar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAA;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAKlD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE;IAC9C,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAA;IAC5C,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC,+BAkBA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { StateDot, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
|
+
import { memberHue } from "./team-formatters.js";
|
|
4
|
+
import { presenceDotState, presenceLabel } from "./TeamPresenceDot.js";
|
|
5
|
+
import css from './sidebar.module.css';
|
|
6
|
+
/**
|
|
7
|
+
* Sidebar Member avatar reusing the conversation identity language: the
|
|
8
|
+
* deterministic member hue and handle initial, with the presence indicator
|
|
9
|
+
* overlaid at the bottom-right so one glyph carries identity and state.
|
|
10
|
+
*/
|
|
11
|
+
export function TeamMemberAvatar({ status, t }) {
|
|
12
|
+
const label = presenceLabel(status, t);
|
|
13
|
+
const state = presenceDotState(status.presence);
|
|
14
|
+
return (_jsx(Tooltip, { label: label, delayMs: 300, children: _jsxs("span", { className: css.agentAvatar, style: { '--team-avatar-hue': memberHue(status.member.memberId) }, role: "img", "aria-label": label, children: [status.member.handle.replace('@', '').slice(0, 1).toUpperCase(), _jsx("span", { className: css.agentAvatarBadge, "aria-hidden": "true", children: state === 'gray' ? _jsx("span", { className: css.unavailableDot }) : _jsx(StateDot, { state: state, size: 8 }) })] }) }));
|
|
15
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TeamFooterProps } from './slots.ts';
|
|
2
|
+
type TeamMembersActionProps = Pick<TeamFooterProps, 'wide' | 'loadMemberGroups' | 't'>;
|
|
3
|
+
export declare function TeamMembersAction({ wide, loadMemberGroups, t }: TeamMembersActionProps): import("react").JSX.Element;
|
|
4
|
+
export {};
|
|
5
|
+
//# sourceMappingURL=TeamMembersAction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TeamMembersAction.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMembersAction.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAKjD,KAAK,sBAAsB,GAAG,IAAI,CAAC,eAAe,EAAE,MAAM,GAAG,kBAAkB,GAAG,GAAG,CAAC,CAAA;AAEtF,wBAAgB,iBAAiB,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,sBAAsB,+BAuDtF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { IconUserOutline16, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
|
+
import { TeamPresenceDot } from "./TeamPresenceDot.js";
|
|
5
|
+
import membersCss from './members.module.css';
|
|
6
|
+
import css from './team.module.css';
|
|
7
|
+
export function TeamMembersAction({ wide, loadMemberGroups, t }) {
|
|
8
|
+
const [panelOpen, setPanelOpen] = useState(false);
|
|
9
|
+
const [groups, setGroups] = useState([]);
|
|
10
|
+
const [loading, setLoading] = useState(false);
|
|
11
|
+
const [error, setError] = useState();
|
|
12
|
+
const triggerRef = useRef(null);
|
|
13
|
+
const contentRef = useRef(null);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!panelOpen)
|
|
16
|
+
return;
|
|
17
|
+
queueMicrotask(() => { contentRef.current?.focus(); });
|
|
18
|
+
}, [panelOpen]);
|
|
19
|
+
const openMembers = () => {
|
|
20
|
+
setPanelOpen(true);
|
|
21
|
+
setLoading(true);
|
|
22
|
+
setError(undefined);
|
|
23
|
+
void loadMemberGroups().then(setGroups).catch(cause => {
|
|
24
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
25
|
+
}).finally(() => { setLoading(false); });
|
|
26
|
+
};
|
|
27
|
+
const closeMembers = () => {
|
|
28
|
+
setPanelOpen(false);
|
|
29
|
+
queueMicrotask(() => { triggerRef.current?.focus(); });
|
|
30
|
+
};
|
|
31
|
+
return (_jsxs(_Fragment, { children: [_jsx(Tooltip, { label: t('members'), delayMs: 500, disabled: wide, children: _jsxs("button", { ref: triggerRef, type: "button", className: wide ? css.settingsAction : `${css.settingsAction} ${css.rail}`, "aria-label": t('members'), "aria-haspopup": "dialog", onClick: openMembers, children: [_jsx(IconUserOutline16, { size: wide ? 16 : 18 }), wide && _jsx("span", { children: t('members') })] }) }), _jsx(Modal, { open: panelOpen, onClose: closeMembers, title: t('members'), closeLabel: t('close'), contentClassName: membersCss.body, children: _jsxs("div", { ref: contentRef, className: membersCss.content, tabIndex: -1, children: [loading && _jsx("p", { className: membersCss.state, role: "status", children: t('loadingAgents') }), !loading && groups.length === 0 && error === undefined && _jsx("p", { className: membersCss.state, children: t('emptyAgents') }), !loading && groups.map(group => (_jsxs("section", { className: membersCss.group, "aria-labelledby": `team-members-${group.workspaceId}`, children: [_jsx("h3", { id: `team-members-${group.workspaceId}`, children: group.workspaceTitle }), group.members.map(status => (_jsxs("div", { className: membersCss.member, children: [_jsx(TeamPresenceDot, { status: status, t: t }), _jsxs("span", { className: membersCss.copy, children: [_jsxs("strong", { children: ["@", status.member.handle] }), _jsx("small", { children: status.member.description })] })] }, status.member.memberId)))] }, group.workspaceId))), error !== undefined && _jsx("p", { className: membersCss.error, role: "alert", children: error })] }) })] }));
|
|
32
|
+
}
|