@wowyuarm/dsh-agent-team 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -2
- package/README.zh.md +10 -2
- package/package.json +2 -2
- package/packages/agent-team/lib/attachments.js +157 -0
- package/packages/agent-team/lib/index.js +274 -5
- package/packages/agent-team/lib/ledger.js +65 -13
- package/packages/agent-team/lib/recovery.js +87 -0
- package/packages/agent-team/lib/spec.js +17 -1
- package/packages/agent-team/lib/typert.host.js +321 -21
- package/packages/agent-team/lib/typert.remote-client.d.ts +9 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +249 -17
- package/packages/agent-team/lib/types/attachments.d.ts +69 -0
- package/packages/agent-team/lib/types/attachments.d.ts.map +1 -0
- package/packages/agent-team/lib/types/index.d.ts +66 -1
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +17 -1
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/recovery.d.ts +42 -0
- package/packages/agent-team/lib/types/recovery.d.ts.map +1 -0
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types.d.ts +85 -2
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/client-agent-team/README.md +2 -2
- package/packages/client-agent-team/README.zh.md +2 -2
- package/packages/client-agent-team/lib/client.js +1176 -281
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +6 -2
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +104 -58
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +5 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +44 -19
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +23 -18
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +4 -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 +40 -4
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +7 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +34 -8
- package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts +10 -0
- package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamRunDivider.js +12 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +3 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +18 -11
- 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 +6 -5
- package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts +22 -0
- package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/attachment-preview.js +35 -0
- package/packages/client-agent-team/lib/types/client/drafts.d.ts +41 -0
- package/packages/client-agent-team/lib/types/client/drafts.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/drafts.js +144 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts +2 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +50 -10
- package/packages/client-agent-team/lib/types/client/locales.d.ts +28 -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 +28 -0
- package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts +33 -0
- package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/multi-menu-field.js +30 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts +16 -1
- package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/navigation.js +45 -8
- package/packages/client-agent-team/lib/types/client/slots.d.ts +18 -2
- 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 +2 -0
- 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 +4 -0
- package/packages/client-agent-team/lib/types/client/team-separators.d.ts +8 -0
- package/packages/client-agent-team/lib/types/client/team-separators.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-separators.js +14 -0
- package/packages/tool-agent-team/lib/index.js +6 -2
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentTeamAddMemberRequest } from '@wowyuarm/dsh-agent-team/types';
|
|
1
|
+
import type { AgentTeamAddMemberRequest, AgentTeamClientMemberStatus } from '@wowyuarm/dsh-agent-team/types';
|
|
2
2
|
import type { WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client';
|
|
3
3
|
import type { TeamSidebarProps } from './slots.ts';
|
|
4
4
|
interface TeamAgentsPanelProps {
|
|
@@ -8,13 +8,17 @@ interface TeamAgentsPanelProps {
|
|
|
8
8
|
readonly loadChannels: TeamSidebarProps['loadChannels'];
|
|
9
9
|
readonly addMember: TeamSidebarProps['addMember'];
|
|
10
10
|
readonly updateMember: TeamSidebarProps['updateMember'];
|
|
11
|
+
readonly recoverMember: TeamSidebarProps['recoverMember'];
|
|
12
|
+
readonly restartMember: TeamSidebarProps['restartMember'];
|
|
11
13
|
readonly joinChannel: TeamSidebarProps['joinChannel'];
|
|
12
14
|
readonly removeChannelMember: TeamSidebarProps['removeChannelMember'];
|
|
13
15
|
readonly loadModels: TeamSidebarProps['loadModels'];
|
|
16
|
+
/** The Member Session currently embedded in the conversation seat, if any. */
|
|
17
|
+
readonly memberSessionId?: AgentTeamClientMemberStatus['member']['sessionId'];
|
|
14
18
|
readonly openMemberSession: TeamSidebarProps['openMemberSession'];
|
|
15
19
|
readonly onCreatingChange: (request: AgentTeamAddMemberRequest, creating: boolean) => void;
|
|
16
20
|
readonly t: TeamSidebarProps['t'];
|
|
17
21
|
}
|
|
18
|
-
export declare function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, joinChannel, removeChannelMember, loadModels, openMemberSession, onCreatingChange, t }: TeamAgentsPanelProps): import("react").JSX.Element;
|
|
22
|
+
export declare function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, recoverMember, restartMember, joinChannel, removeChannelMember, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }: TeamAgentsPanelProps): import("react").JSX.Element;
|
|
19
23
|
export {};
|
|
20
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,
|
|
1
|
+
{"version":3,"file":"TeamAgentsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAgentsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EAEzB,2BAA2B,EAM5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAGzE,OAAO,KAAK,EAAiD,gBAAgB,EAAE,MAAM,YAAY,CAAA;AASjG,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,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,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,aAAa,EAAE,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BAmK/Q"}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { Button, IconChevronDownOutline14, IconEditOutline16, IconPlusOutline16, Input, Menu, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
|
+
import { Button, IconChevronDownOutline14, IconEditOutline16, IconPlayOutline16, IconPlusOutline16, IconRefreshOutline14, Input, Menu, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { TeamMemberAvatar } from "./TeamMemberAvatar.js";
|
|
5
5
|
import { TeamRowMenu } from "./TeamRowMenu.js";
|
|
6
6
|
import { TeamSidebarSection } from "./TeamSidebarSection.js";
|
|
7
|
+
import { MultiMenuField } from "./multi-menu-field.js";
|
|
7
8
|
import { useChannelMembership } from "./team-membership.js";
|
|
8
9
|
import createCss from './create.module.css';
|
|
9
10
|
import css from './sidebar.module.css';
|
|
10
|
-
export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, joinChannel, removeChannelMember, loadModels, openMemberSession, onCreatingChange, t }) {
|
|
11
|
+
export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, recoverMember, restartMember, joinChannel, removeChannelMember, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }) {
|
|
11
12
|
const [members, setMembers] = useState([]);
|
|
12
13
|
const [channels, setChannels] = useState([]);
|
|
13
14
|
const [channelRefs, setChannelRefs] = useState([]);
|
|
@@ -16,6 +17,7 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
|
|
|
16
17
|
const [formOpen, setFormOpen] = useState(false);
|
|
17
18
|
const [handle, setHandle] = useState('');
|
|
18
19
|
const [description, setDescription] = useState('');
|
|
20
|
+
const [model, setModel] = useState(undefined);
|
|
19
21
|
const [creating, setCreating] = useState(false);
|
|
20
22
|
const [retryRequest, setRetryRequest] = useState();
|
|
21
23
|
const triggerRef = useRef(null);
|
|
@@ -68,6 +70,7 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
|
|
|
68
70
|
});
|
|
69
71
|
setHandle('');
|
|
70
72
|
setDescription('');
|
|
73
|
+
setModel(undefined);
|
|
71
74
|
setChannelRefs([]);
|
|
72
75
|
setFormOpen(false);
|
|
73
76
|
if (result.value.status.presence === 'unavailable') {
|
|
@@ -95,12 +98,11 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
|
|
|
95
98
|
event.preventDefault();
|
|
96
99
|
const normalizedHandle = handle.trim();
|
|
97
100
|
const normalizedDescription = description.trim();
|
|
98
|
-
if (normalizedHandle.length === 0 ||
|
|
99
|
-
return;
|
|
100
|
-
if (channelRefs.length === 0)
|
|
101
|
+
if (normalizedHandle.length === 0 || creating)
|
|
101
102
|
return;
|
|
102
103
|
const sameRequest = retryRequest !== undefined && retryRequest.workspaceId === workspaceId
|
|
103
104
|
&& retryRequest.handle === normalizedHandle && retryRequest.description === normalizedDescription
|
|
105
|
+
&& sameModel(retryRequest.model, model)
|
|
104
106
|
&& retryRequest.channelRefs.length === channelRefs.length && retryRequest.channelRefs.every(ref => channelRefs.includes(ref));
|
|
105
107
|
void provision(sameRequest ? retryRequest : {
|
|
106
108
|
requestId: crypto.randomUUID(),
|
|
@@ -109,47 +111,66 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
|
|
|
109
111
|
description: normalizedDescription,
|
|
110
112
|
presetId: 'team-member',
|
|
111
113
|
channelRefs,
|
|
114
|
+
...(model === undefined ? {} : { model }),
|
|
112
115
|
});
|
|
113
116
|
};
|
|
114
|
-
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
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
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 => {
|
|
118
|
+
setChannelRefs(current => current.includes(ref) ? current.filter(item => item !== ref) : [...current, ref]);
|
|
119
|
+
setRetryRequest(undefined);
|
|
120
|
+
}, 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: members.map(status => (_jsx(AgentRow, { status: status, ...(memberSessionId === undefined ? {} : { current: status.member.sessionId === memberSessionId }), channels: channels, loadChannels: loadChannels, updateMember: updateMember, recoverMember: recoverMember, restartMember: restartMember, 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') })] }))] }));
|
|
118
121
|
}
|
|
119
122
|
/**
|
|
120
123
|
* One sidebar Agent row: the select button opens the Member's own Session
|
|
121
124
|
* conversation page, the avatar carries identity plus the presence badge, and
|
|
122
125
|
* the row menu opens the editor.
|
|
123
126
|
*/
|
|
124
|
-
function AgentRow({ status, channels, loadChannels, updateMember, joinChannel, removeChannelMember, loadModels, openMemberSession, onUpdated, t }) {
|
|
127
|
+
function AgentRow({ status, current, channels, loadChannels, updateMember, recoverMember, restartMember, joinChannel, removeChannelMember, loadModels, openMemberSession, onUpdated, t }) {
|
|
125
128
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
126
129
|
const [editing, setEditing] = useState(false);
|
|
127
|
-
|
|
130
|
+
const resume = async () => {
|
|
131
|
+
await recoverMember({
|
|
132
|
+
requestId: crypto.randomUUID(),
|
|
133
|
+
workspaceId: status.member.workspaceId,
|
|
134
|
+
memberId: status.member.memberId,
|
|
135
|
+
});
|
|
136
|
+
await onUpdated();
|
|
137
|
+
};
|
|
138
|
+
const restart = async () => {
|
|
139
|
+
await restartMember({
|
|
140
|
+
requestId: crypto.randomUUID(),
|
|
141
|
+
workspaceId: status.member.workspaceId,
|
|
142
|
+
memberId: status.member.memberId,
|
|
143
|
+
});
|
|
144
|
+
await onUpdated();
|
|
145
|
+
};
|
|
146
|
+
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, 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: [
|
|
147
|
+
{ id: 'edit', label: t('editAgent'), icon: _jsx(IconEditOutline16, {}) },
|
|
148
|
+
...(status.presence === 'error' ? [{ id: 'resume', label: t('resumeAgent'), icon: _jsx(IconPlayOutline16, {}) }] : []),
|
|
149
|
+
{ id: 'restart', label: t('restartAgent'), icon: _jsx(IconRefreshOutline14, {}) },
|
|
150
|
+
], onSelect: (id) => {
|
|
151
|
+
if (id === 'resume')
|
|
152
|
+
void resume();
|
|
153
|
+
else if (id === 'restart')
|
|
154
|
+
void restart();
|
|
155
|
+
else
|
|
156
|
+
setEditing(true);
|
|
157
|
+
}, onOpenChange: setMenuOpen }) })] }), editing && (_jsx(AgentEditorDialog, { status: status, channels: channels, loadChannels: loadChannels, updateMember: updateMember, joinChannel: joinChannel, removeChannelMember: removeChannelMember, loadModels: loadModels, onCommitted: onUpdated, onClose: () => { setEditing(false); }, t: t }))] }));
|
|
128
158
|
}
|
|
129
159
|
/** Model option key inside one editor; opaque and resolved against the loaded groups. */
|
|
130
160
|
function modelKey(provider, model) {
|
|
131
161
|
return `${provider}\u0000${model}`;
|
|
132
162
|
}
|
|
133
163
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
164
|
+
* Shared provider/model dropdown for the create and edit forms. The option
|
|
165
|
+
* list rides the shared Menu primitive (one leading "follow Host default"
|
|
166
|
+
* row, then non-selectable provider headings) with a capped, internally
|
|
167
|
+
* scrolling card so growing model catalogs cannot stretch the dialog.
|
|
137
168
|
*/
|
|
138
|
-
function
|
|
139
|
-
const memberId = status.member.memberId;
|
|
140
|
-
const [handle, setHandle] = useState(status.member.handle);
|
|
141
|
-
const [description, setDescription] = useState(status.member.description);
|
|
142
|
-
const [model, setModel] = useState(status.member.model === undefined ? undefined : [status.member.model.provider, status.member.model.model]);
|
|
169
|
+
function ModelPickerField({ model, onModelChange, loadModels, disabled, t }) {
|
|
143
170
|
const [groups, setGroups] = useState();
|
|
144
171
|
const [modelsError, setModelsError] = useState();
|
|
145
|
-
const [
|
|
146
|
-
const [
|
|
147
|
-
const [error, setError] = useState();
|
|
148
|
-
const pendingRequest = useRef();
|
|
149
|
-
// Membership comes from a fresh projection at open time so stale checkbox
|
|
150
|
-
// state never flips a committed fact backwards.
|
|
151
|
-
const [memberships, setMemberships] = useState();
|
|
152
|
-
const [loadError, setLoadError] = useState();
|
|
172
|
+
const [open, setModelOpen] = useState(false);
|
|
173
|
+
const [effortOpen, setEffortOpen] = useState(false);
|
|
153
174
|
useEffect(() => {
|
|
154
175
|
let mounted = true;
|
|
155
176
|
void loadModels().then(result => {
|
|
@@ -165,6 +186,56 @@ function AgentEditorDialog({ status, channels, loadChannels, updateMember, joinC
|
|
|
165
186
|
});
|
|
166
187
|
return () => { mounted = false; };
|
|
167
188
|
}, [loadModels]);
|
|
189
|
+
const items = [{ id: '', label: t('modelFollowDefault') }];
|
|
190
|
+
const byKey = new Map();
|
|
191
|
+
for (const group of groups ?? []) {
|
|
192
|
+
items.push({ type: 'label', id: `model-group:${group.id}`, text: group.name });
|
|
193
|
+
for (const entry of group.models) {
|
|
194
|
+
const key = modelKey(group.id, entry.id);
|
|
195
|
+
byKey.set(key, { provider: group.id, id: entry.id, name: entry.name, efforts: entry.reasoning?.efforts ?? [] });
|
|
196
|
+
items.push({ id: key, label: entry.name });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const selectedModelKey = model === undefined ? '' : modelKey(model.provider, model.model);
|
|
200
|
+
const triggerLabel = model === undefined
|
|
201
|
+
? t('modelFollowDefault')
|
|
202
|
+
: byKey.get(selectedModelKey)?.name ?? `${model.provider} / ${model.model}`;
|
|
203
|
+
// The effort sub-row only makes sense for a pinned model with adapter-exposed
|
|
204
|
+
// efforts; following the Host default inherits the operator's whole selection.
|
|
205
|
+
const efforts = model === undefined ? [] : byKey.get(selectedModelKey)?.efforts ?? [];
|
|
206
|
+
const effortItems = [{ id: '', label: t('effortFollowDefault') }, ...efforts.map(effort => ({ id: effort.id, label: effort.name }))];
|
|
207
|
+
const selectedEffort = model?.reasoningEffort ?? '';
|
|
208
|
+
const effortTriggerLabel = model === undefined || selectedEffort === ''
|
|
209
|
+
? t('effortFollowDefault')
|
|
210
|
+
: efforts.find(effort => effort.id === selectedEffort)?.name ?? selectedEffort;
|
|
211
|
+
return _jsxs("div", { className: createCss.field, children: [_jsx("span", { children: t('memberModel') }), groups === undefined && modelsError === undefined && _jsx("small", { className: css.editHint, children: t('modelsLoading') }), modelsError !== undefined && _jsx("small", { className: css.editHint, children: t('modelsLoadFailed', { message: modelsError }) }), groups !== undefined && (_jsx(Menu, { open: open, portal: true, className: createCss.menuCap, items: items, selectedId: selectedModelKey, onSelect: key => {
|
|
212
|
+
setModelOpen(false);
|
|
213
|
+
const choice = byKey.get(key);
|
|
214
|
+
onModelChange(choice === undefined ? undefined : { provider: choice.provider, model: choice.id });
|
|
215
|
+
}, onClose: () => { setModelOpen(false); }, anchor: _jsxs("button", { type: "button", className: createCss.selectTrigger, "aria-label": t('memberModel'), "aria-haspopup": "listbox", "aria-expanded": open, disabled: disabled, onClick: () => { setModelOpen(value => !value); }, children: [_jsx("span", { className: createCss.selectValue, children: triggerLabel }), _jsx("span", { className: `${createCss.chevron} ${open ? createCss.chevronOpen : ''}`, "aria-hidden": true, children: _jsx(IconChevronDownOutline14, {}) })] }) })), model !== undefined && efforts.length > 0 && (_jsx(Menu, { open: effortOpen, portal: true, className: createCss.menuCap, items: effortItems, selectedId: selectedEffort, onSelect: key => {
|
|
216
|
+
setEffortOpen(false);
|
|
217
|
+
onModelChange(key === ''
|
|
218
|
+
? { provider: model.provider, model: model.model }
|
|
219
|
+
: { provider: model.provider, model: model.model, reasoningEffort: key });
|
|
220
|
+
}, onClose: () => { setEffortOpen(false); }, anchor: _jsxs("button", { type: "button", className: createCss.selectTrigger, "aria-label": t('reasoningEffort'), "aria-haspopup": "listbox", "aria-expanded": effortOpen, disabled: disabled, onClick: () => { setEffortOpen(value => !value); }, children: [_jsx("span", { className: createCss.selectValue, children: `${t('reasoningEffort')} · ${effortTriggerLabel}` }), _jsx("span", { className: `${createCss.chevron} ${effortOpen ? createCss.chevronOpen : ''}`, "aria-hidden": true, children: _jsx(IconChevronDownOutline14, {}) })] }) }))] });
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Agent editor: handle, description, and per-Member model selection commit
|
|
224
|
+
* through one durable update; Channel membership below keeps its own
|
|
225
|
+
* immediate add/remove flow.
|
|
226
|
+
*/
|
|
227
|
+
function AgentEditorDialog({ status, channels, loadChannels, updateMember, joinChannel, removeChannelMember, loadModels, onCommitted, onClose, t }) {
|
|
228
|
+
const memberId = status.member.memberId;
|
|
229
|
+
const [handle, setHandle] = useState(status.member.handle);
|
|
230
|
+
const [description, setDescription] = useState(status.member.description);
|
|
231
|
+
const [model, setModel] = useState(status.member.model);
|
|
232
|
+
const [saving, setSaving] = useState(false);
|
|
233
|
+
const [error, setError] = useState();
|
|
234
|
+
const pendingRequest = useRef();
|
|
235
|
+
// Membership comes from a fresh projection at open time so stale checkbox
|
|
236
|
+
// state never flips a committed fact backwards.
|
|
237
|
+
const [memberships, setMemberships] = useState();
|
|
238
|
+
const [loadError, setLoadError] = useState();
|
|
168
239
|
useEffect(() => {
|
|
169
240
|
let mounted = true;
|
|
170
241
|
void loadChannels({ workspaceId: status.member.workspaceId, topLevelOnly: true, includeActivities: false, limit: 1 }).then(result => {
|
|
@@ -190,43 +261,23 @@ function AgentEditorDialog({ status, channels, loadChannels, updateMember, joinC
|
|
|
190
261
|
});
|
|
191
262
|
// A successful join appends the durable fact; removal filters it out above.
|
|
192
263
|
const joinedChannels = new Set((memberships ?? []).filter(item => item.memberId === memberId).map(item => item.channelRef));
|
|
193
|
-
// The provider-grouped option list rides the shared Menu primitive: one
|
|
194
|
-
// leading "follow Host default" row, then a non-selectable heading per
|
|
195
|
-
// provider group. Keys resolve back through the map, never by parsing.
|
|
196
|
-
const modelChoices = (() => {
|
|
197
|
-
const items = [{ id: '', label: t('modelFollowDefault') }];
|
|
198
|
-
const byKey = new Map();
|
|
199
|
-
for (const group of groups ?? []) {
|
|
200
|
-
items.push({ type: 'label', id: `model-group:${group.id}`, text: group.name });
|
|
201
|
-
for (const entry of group.models) {
|
|
202
|
-
const key = modelKey(group.id, entry.id);
|
|
203
|
-
byKey.set(key, { provider: group.id, id: entry.id, name: entry.name });
|
|
204
|
-
items.push({ id: key, label: entry.name });
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return { items, byKey };
|
|
208
|
-
})();
|
|
209
|
-
const selectedModelKey = model === undefined ? '' : modelKey(model[0], model[1]);
|
|
210
|
-
const modelTriggerLabel = model === undefined
|
|
211
|
-
? t('modelFollowDefault')
|
|
212
|
-
: modelChoices.byKey.get(selectedModelKey)?.name ?? `${model[0]} / ${model[1]}`;
|
|
213
264
|
const dirty = handle.trim() !== status.member.handle || description.trim() !== status.member.description
|
|
214
|
-
|| !sameModel(model, status.member.model
|
|
265
|
+
|| !sameModel(model, status.member.model);
|
|
215
266
|
const submit = async (event) => {
|
|
216
267
|
event.preventDefault();
|
|
217
268
|
const normalizedHandle = handle.trim();
|
|
218
269
|
const normalizedDescription = description.trim();
|
|
219
|
-
if (saving || !dirty || normalizedHandle.length === 0
|
|
270
|
+
if (saving || !dirty || normalizedHandle.length === 0)
|
|
220
271
|
return;
|
|
221
272
|
const payload = {
|
|
222
273
|
memberId,
|
|
223
274
|
handle: normalizedHandle,
|
|
224
275
|
description: normalizedDescription,
|
|
225
|
-
...(model === undefined ? {} : { model
|
|
276
|
+
...(model === undefined ? {} : { model }),
|
|
226
277
|
};
|
|
227
278
|
const samePending = pendingRequest.current !== undefined && pendingRequest.current.memberId === payload.memberId
|
|
228
279
|
&& pendingRequest.current.handle === payload.handle && pendingRequest.current.description === payload.description
|
|
229
|
-
&& sameModel(pendingRequest.current.model
|
|
280
|
+
&& sameModel(pendingRequest.current.model, model);
|
|
230
281
|
const request = samePending ? pendingRequest.current : {
|
|
231
282
|
requestId: crypto.randomUUID(),
|
|
232
283
|
...payload,
|
|
@@ -252,12 +303,7 @@ function AgentEditorDialog({ status, channels, loadChannels, updateMember, joinC
|
|
|
252
303
|
setSaving(false);
|
|
253
304
|
}
|
|
254
305
|
};
|
|
255
|
-
return (_jsx(Modal, { open: true, onClose: onClose, title: t('editAgent'), description: `@${status.member.handle}`, 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-agent-edit-form", variant: "primary", disabled: saving || !dirty || handle.trim().length === 0
|
|
256
|
-
setModelOpen(false);
|
|
257
|
-
pendingRequest.current = undefined;
|
|
258
|
-
const choice = modelChoices.byKey.get(key);
|
|
259
|
-
setModel(choice === undefined ? undefined : [choice.provider, choice.id]);
|
|
260
|
-
}, onClose: () => { setModelOpen(false); }, anchor: _jsxs("button", { type: "button", className: createCss.selectTrigger, "aria-label": t('memberModel'), "aria-haspopup": "listbox", "aria-expanded": modelOpen, disabled: saving, onClick: () => { setModelOpen(value => !value); }, children: [_jsx("span", { className: createCss.selectValue, children: modelTriggerLabel }), _jsx("span", { className: `${createCss.chevron} ${modelOpen ? createCss.chevronOpen : ''}`, "aria-hidden": true, children: _jsx(IconChevronDownOutline14, {}) })] }) }))] }), _jsxs("fieldset", { className: createCss.memberPicker, disabled: saving, children: [_jsx("legend", { children: t('channelMembersSection') }), channels.map(channel => {
|
|
306
|
+
return (_jsx(Modal, { open: true, onClose: onClose, title: t('editAgent'), description: `@${status.member.handle}`, 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-agent-edit-form", variant: "primary", disabled: saving || !dirty || handle.trim().length === 0, children: saving ? t('editSaving') : t('editSave') })] }), children: _jsxs("form", { id: "team-agent-edit-form", className: createCss.form, onSubmit: event => { void submit(event); }, 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); pendingRequest.current = undefined; }, disabled: saving, 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); pendingRequest.current = undefined; }, disabled: saving })] }), _jsx(ModelPickerField, { model: model, onModelChange: choice => { pendingRequest.current = undefined; setModel(choice); }, loadModels: loadModels, disabled: saving, t: t }), _jsxs("fieldset", { className: createCss.memberPicker, disabled: saving, children: [_jsx("legend", { children: t('channelMembersSection') }), channels.map(channel => {
|
|
261
307
|
const joined = joinedChannels.has(channel.channelRef);
|
|
262
308
|
const rowPending = membership.pending.has(channel.channelRef);
|
|
263
309
|
const disabled = rowPending || (!joined && status.presence === 'unavailable');
|
|
@@ -270,5 +316,5 @@ function sameModel(left, right) {
|
|
|
270
316
|
return true;
|
|
271
317
|
if (left === undefined || right === undefined)
|
|
272
318
|
return false;
|
|
273
|
-
return left
|
|
319
|
+
return left.provider === right.provider && left.model === right.model && left.reasoningEffort === right.reasoningEffort;
|
|
274
320
|
}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import type { AgentTeamChannelRef } from '@wowyuarm/dsh-agent-team/types';
|
|
2
2
|
import type { WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client';
|
|
3
3
|
import type { TeamConversationProps } from './slots.ts';
|
|
4
|
+
import type { TeamDraftStore } from './drafts.ts';
|
|
4
5
|
interface TeamChannelPageProps {
|
|
5
6
|
readonly workspaceId: WorkspaceId;
|
|
6
7
|
readonly channelRef: AgentTeamChannelRef;
|
|
7
8
|
readonly loadChannels: TeamConversationProps['loadChannels'];
|
|
8
9
|
readonly subscribeChanges: TeamConversationProps['subscribeChanges'];
|
|
9
10
|
readonly loadMembers: TeamConversationProps['loadMembers'];
|
|
11
|
+
readonly drafts: TeamDraftStore;
|
|
12
|
+
readonly getAttachment: TeamConversationProps['getAttachment'];
|
|
13
|
+
readonly putAttachment: TeamConversationProps['putAttachment'];
|
|
10
14
|
readonly sendMessage: TeamConversationProps['sendMessage'];
|
|
11
15
|
readonly joinChannel: TeamConversationProps['joinChannel'];
|
|
12
16
|
readonly removeChannelMember: TeamConversationProps['removeChannelMember'];
|
|
@@ -14,6 +18,6 @@ interface TeamChannelPageProps {
|
|
|
14
18
|
readonly backToChannels: TeamConversationProps['backToChannels'];
|
|
15
19
|
readonly t: TeamConversationProps['t'];
|
|
16
20
|
}
|
|
17
|
-
export declare function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, selectThread, backToChannels, t }: TeamChannelPageProps): import("react").JSX.Element;
|
|
21
|
+
export declare function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, backToChannels, t }: TeamChannelPageProps): import("react").JSX.Element;
|
|
18
22
|
export {};
|
|
19
23
|
//# sourceMappingURL=TeamChannelPage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamChannelPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA+B,mBAAmB,EAAoF,MAAM,gCAAgC,CAAA;AACxL,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"TeamChannelPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA+B,mBAAmB,EAAoF,MAAM,gCAAgC,CAAA;AACxL,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAGvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAa/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,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,cAAc,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BA8RnP"}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Fragment, useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
3
3
|
import { Button, IconChevronLeftOutline14, IconChevronRightOutline14, Modal, StateDot } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
|
+
import { bytesToBase64 } from "./attachment-preview.js";
|
|
4
5
|
import { TeamComposer } from "./TeamComposer.js";
|
|
5
6
|
import { TeamPresenceDot } from "./TeamPresenceDot.js";
|
|
6
7
|
import { TeamMessage } from "./TeamMessage.js";
|
|
8
|
+
import { TeamRunDivider } from "./TeamRunDivider.js";
|
|
7
9
|
import { formatTaskStatus, taskStatusDot, mentionNamesOf } from "./team-formatters.js";
|
|
8
10
|
import { useChannelMembership } from "./team-membership.js";
|
|
9
11
|
import { useTimelineScroll } from "./timeline-scroll.js";
|
|
10
|
-
import { chunkRunsWithDays } from "./team-separators.js";
|
|
12
|
+
import { chunkRunsWithDays, isRunGap } from "./team-separators.js";
|
|
11
13
|
import channelCss from './channel.module.css';
|
|
12
14
|
import css from './conversation.module.css';
|
|
13
15
|
import threadCss from './thread.module.css';
|
|
@@ -27,17 +29,20 @@ function mergeChannelView(current, fresh) {
|
|
|
27
29
|
hasMore: fresh.hasMore || current.cursor < fresh.cursor,
|
|
28
30
|
};
|
|
29
31
|
}
|
|
30
|
-
export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, selectThread, backToChannels, t }) {
|
|
32
|
+
export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, backToChannels, t }) {
|
|
31
33
|
const [view, setView] = useState();
|
|
32
34
|
const [members, setMembers] = useState([]);
|
|
33
|
-
const [draft, setDraft] = useState('');
|
|
34
35
|
const [error, setError] = useState();
|
|
36
|
+
const [pendingFiles, setPendingFiles] = useState([]);
|
|
35
37
|
const [statusMessage, setStatusMessage] = useState();
|
|
36
38
|
const [loading, setLoading] = useState(true);
|
|
37
39
|
const [pending, setPending] = useState(false);
|
|
38
40
|
const [loadingOlder, setLoadingOlder] = useState(false);
|
|
39
|
-
const [recipients, setRecipients] = useState(new Set());
|
|
40
41
|
const [managingMembers, setManagingMembers] = useState(false);
|
|
42
|
+
// The composer draft lives in the keyed draft cache: view switches unmount
|
|
43
|
+
// this page, and a refresh must not cost the half-written message either.
|
|
44
|
+
const draftKey = `channel:${channelRef}`;
|
|
45
|
+
const { draft, recipients } = useSyncExternalStore(drafts.subscribe, () => drafts.getSnapshot(draftKey));
|
|
41
46
|
const manageTriggerRef = useRef(null);
|
|
42
47
|
const memberListRef = useRef(null);
|
|
43
48
|
const mountedRef = useRef(false);
|
|
@@ -112,7 +117,6 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
112
117
|
setView(undefined);
|
|
113
118
|
setError(undefined);
|
|
114
119
|
setLoading(true);
|
|
115
|
-
setRecipients(new Set());
|
|
116
120
|
setManagingMembers(false);
|
|
117
121
|
void refresh();
|
|
118
122
|
const disposers = [
|
|
@@ -179,19 +183,39 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
179
183
|
// definitive outcomes (committed or rejected) start the next send fresh.
|
|
180
184
|
const pendingSendId = useRef();
|
|
181
185
|
const send = async () => {
|
|
186
|
+
console.log('DBG send entry pending=%s draft=%j files=%d', pending, draft, pendingFiles.length);
|
|
182
187
|
if (pending || draft.trim() === '')
|
|
183
188
|
return;
|
|
184
189
|
const recipientIds = [...recipients].sort();
|
|
185
190
|
const requestId = pendingSendId.current ?? crypto.randomUUID();
|
|
186
191
|
pendingSendId.current = requestId;
|
|
187
|
-
const request = {
|
|
188
|
-
requestId, workspaceId,
|
|
189
|
-
channelRef, body: draft.trim(), recipients: recipientIds,
|
|
190
|
-
};
|
|
191
192
|
setPending(true);
|
|
192
193
|
setError(undefined);
|
|
193
194
|
setStatusMessage(undefined);
|
|
194
195
|
try {
|
|
196
|
+
// Upload chosen files first; any failure aborts the send with the
|
|
197
|
+
// existing error surface and keeps the chips for a retry.
|
|
198
|
+
const attachmentIds = [];
|
|
199
|
+
for (const file of pendingFiles) {
|
|
200
|
+
const uploaded = await putAttachment({
|
|
201
|
+
requestId: crypto.randomUUID(),
|
|
202
|
+
workspaceId,
|
|
203
|
+
name: file.name,
|
|
204
|
+
mediaType: file.type === '' ? undefined : file.type,
|
|
205
|
+
bytesBase64: bytesToBase64(new Uint8Array(await file.arrayBuffer())),
|
|
206
|
+
});
|
|
207
|
+
if (!uploaded.ok) {
|
|
208
|
+
pendingSendId.current = undefined;
|
|
209
|
+
setError(uploaded.error.message);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
attachmentIds.push(uploaded.value.attachmentId);
|
|
213
|
+
}
|
|
214
|
+
const request = {
|
|
215
|
+
requestId, workspaceId,
|
|
216
|
+
channelRef, body: draft.trim(), recipients: recipientIds,
|
|
217
|
+
...(attachmentIds.length === 0 ? {} : { attachments: attachmentIds }),
|
|
218
|
+
};
|
|
195
219
|
const result = await sendMessage(request);
|
|
196
220
|
if (!result.ok) {
|
|
197
221
|
pendingSendId.current = undefined;
|
|
@@ -200,8 +224,8 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
200
224
|
else if (result.value.kind === 'committed') {
|
|
201
225
|
pendingSendId.current = undefined;
|
|
202
226
|
await refresh();
|
|
203
|
-
|
|
204
|
-
|
|
227
|
+
drafts.clear(draftKey);
|
|
228
|
+
setPendingFiles([]);
|
|
205
229
|
setStatusMessage(undefined);
|
|
206
230
|
}
|
|
207
231
|
else if (result.value.kind === 'confirmation_required') {
|
|
@@ -233,11 +257,12 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
233
257
|
const senderStatus = members.find(member => member.member.memberId === item.message.sender);
|
|
234
258
|
const human = item.message.sender === view.humanMemberId;
|
|
235
259
|
const sender = human ? t('human') : senderStatus?.member.handle ?? item.message.sender;
|
|
236
|
-
return
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
260
|
+
return _jsxs(Fragment, { children: [isRunGap(index > 0 ? block.items[index - 1].message.occurredAt : undefined, item.message.occurredAt)
|
|
261
|
+
&& _jsx(TeamRunDivider, { occurredAt: item.message.occurredAt }), _jsx(TeamMessage, { senderName: sender, memberId: item.message.sender, human: human, body: item.message.body, attachments: item.message.attachments, loadAttachment: getAttachment, t: t, occurredAt: item.message.occurredAt, mentionNames: mentionNamesOf(item.mentions, mentionHandlesMap), grouped: index > 0, ...(senderStatus === undefined ? {} : { senderTitle: senderStatus.member.description }), children: item.message.topLevel && _jsxs("button", { type: "button", className: channelCss.taskFooter, "aria-label": t('openTask', { number: item.taskNumber }), onClick: () => { selectThread(item.task.taskRef, item.thread.threadRef, channelRef, item.taskNumber); }, children: [_jsx("span", { className: channelCss.taskDot, children: (() => {
|
|
262
|
+
const dot = taskStatusDot(item.task.status);
|
|
263
|
+
return dot === 'ongoing' || dot === 'warning' || dot === 'done'
|
|
264
|
+
? _jsx(StateDot, { size: 8, state: dot })
|
|
265
|
+
: _jsx("span", { className: channelCss.taskDotQuiet, "data-variant": dot });
|
|
266
|
+
})() }), _jsx("span", { className: channelCss.taskNumber, children: t('taskLabel', { number: item.taskNumber }) }), _jsx("span", { className: channelCss.taskStatus, children: formatTaskStatus(item.task.status, t) }), _jsx("span", { className: channelCss.taskCount, children: t('taskMessageCount', { count: item.messageCount }) }), _jsx("span", { className: channelCss.taskArrow, "aria-hidden": "true", children: _jsx(IconChevronRightOutline14, { size: 12 }) })] }) })] }, item.message.messageRef);
|
|
267
|
+
}) }, `run-${block.items[0].message.messageRef}`))] }) }), channel !== undefined ? _jsx(TeamComposer, { members: channelMembers, recipients: recipients, draft: draft, pending: pending, ...(statusMessage === undefined ? {} : { confirmation: statusMessage }), ...(error === undefined ? {} : { error }), onDraftChange: next => { drafts.writeDraft(draftKey, next); setStatusMessage(undefined); }, onRecipientsChange: next => { drafts.writeRecipients(draftKey, next); setStatusMessage(undefined); }, onSubmit: () => { void send(); }, pendingFiles: pendingFiles, onFilesChange: setPendingFiles, t: t }, draftKey) : _jsx("div", {})] });
|
|
243
268
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamChannelsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EAEzB,mBAAmB,EAOpB,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,EAOpB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AASlD,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,+BA8K9D"}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
3
|
import { Button, IconEditOutline16, IconPlusOutline16, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { TeamPresenceDot } from "./TeamPresenceDot.js";
|
|
5
|
+
import { MultiMenuField } from "./multi-menu-field.js";
|
|
5
6
|
import { TeamRowMenu } from "./TeamRowMenu.js";
|
|
6
7
|
import { TeamSidebarSection } from "./TeamSidebarSection.js";
|
|
7
8
|
import { useChannelMembership } from "./team-membership.js";
|
|
@@ -76,7 +77,7 @@ export function TeamChannelsPanel(props) {
|
|
|
76
77
|
};
|
|
77
78
|
const submit = async (event) => {
|
|
78
79
|
event.preventDefault();
|
|
79
|
-
if (mutating || name.trim() === ''
|
|
80
|
+
if (mutating || name.trim() === '')
|
|
80
81
|
return;
|
|
81
82
|
const payload = { workspaceId, name: name.trim(), description: description.trim(), memberIds: [...selected] };
|
|
82
83
|
const samePending = pendingCreate !== undefined && pendingCreate.workspaceId === payload.workspaceId
|
|
@@ -110,21 +111,25 @@ export function TeamChannelsPanel(props) {
|
|
|
110
111
|
setMutating(false);
|
|
111
112
|
}
|
|
112
113
|
};
|
|
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() === ''
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
114
|
+
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() === '', 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: [_jsxs("span", { children: [t('channelDescription'), t('optionalSuffix')] }), _jsx(Input, { className: createCss.input, value: description, placeholder: t('agentDescriptionPlaceholder'), disabled: mutating, onChange: event => { setDescription(event.target.value); setPendingCreate(undefined); } })] }), _jsx(MultiMenuField, { label: t('initialMembers'), disabled: mutating, options: [
|
|
115
|
+
...creatingAgents.map(request => ({ id: request.requestId, label: request.handle, disabled: true, hint: t('memberCreatingReason') })),
|
|
116
|
+
...members.map(status => ({
|
|
117
|
+
id: status.member.memberId,
|
|
118
|
+
label: status.member.handle,
|
|
119
|
+
...(status.presence === 'unavailable' ? { disabled: true, hint: t('memberUnavailableReason') } : {}),
|
|
120
|
+
icon: _jsx(TeamPresenceDot, { status: status, t: t }),
|
|
121
|
+
})),
|
|
122
|
+
], selected: [...selected], onToggle: id => {
|
|
123
|
+
setSelected(current => {
|
|
124
|
+
const next = new Set(current);
|
|
125
|
+
if (next.has(id))
|
|
126
|
+
next.delete(id);
|
|
127
|
+
else
|
|
128
|
+
next.add(id);
|
|
129
|
+
return next;
|
|
130
|
+
});
|
|
131
|
+
setPendingCreate(undefined);
|
|
132
|
+
}, 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: view?.channels.map(channel => {
|
|
128
133
|
const joined = membership.get(channel.channelRef) ?? new Set();
|
|
129
134
|
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
135
|
}) })] }), !formOpen && error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] }));
|
|
@@ -154,7 +159,7 @@ function ChannelEditorDialog({ channel, members, joinedIds, updateChannel, joinC
|
|
|
154
159
|
event.preventDefault();
|
|
155
160
|
const normalizedName = name.trim();
|
|
156
161
|
const normalizedDescription = description.trim();
|
|
157
|
-
if (saving || !dirty || normalizedName === ''
|
|
162
|
+
if (saving || !dirty || normalizedName === '')
|
|
158
163
|
return;
|
|
159
164
|
const samePending = pendingRequest.current !== undefined && pendingRequest.current.channelRef === channel.channelRef
|
|
160
165
|
&& pendingRequest.current.name === normalizedName && pendingRequest.current.description === normalizedDescription;
|
|
@@ -186,7 +191,7 @@ function ChannelEditorDialog({ channel, members, joinedIds, updateChannel, joinC
|
|
|
186
191
|
setSaving(false);
|
|
187
192
|
}
|
|
188
193
|
};
|
|
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() === ''
|
|
194
|
+
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() === '', 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: [_jsxs("span", { children: [t('channelDescription'), t('optionalSuffix')] }), _jsx(Input, { className: createCss.input, value: description, placeholder: t('agentDescriptionPlaceholder'), 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
195
|
const joined = joinedIds.has(status.member.memberId);
|
|
191
196
|
const rowPending = membership.pending.has(status.member.memberId);
|
|
192
197
|
const disabled = rowPending || (!joined && status.presence === 'unavailable');
|