@wowyuarm/dsh-agent-team 0.1.14-alpha.2 → 0.1.14
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 +5 -3
- package/README.zh.md +5 -3
- package/cordis.patch.yml +2 -185
- package/package.json +52 -46
- package/packages/agent-team/README.md +1 -1
- package/packages/agent-team/README.zh.md +1 -1
- package/packages/agent-team/core-skills/member-memory-manager/SKILL.md +30 -0
- package/packages/agent-team/lib/context-continuity-host.js +91 -0
- package/packages/agent-team/lib/context-projection.js +266 -489
- package/packages/agent-team/lib/context-source.js +17 -106
- package/packages/agent-team/lib/human-avatar.js +61 -0
- package/packages/agent-team/lib/human-profile.js +46 -0
- package/packages/agent-team/lib/human-update-check.js +119 -0
- package/packages/agent-team/lib/index.js +408 -264
- package/packages/agent-team/lib/ledger.js +120 -15
- package/packages/agent-team/lib/member-context.js +9 -8
- package/packages/agent-team/lib/member-runtime.js +1 -1
- package/packages/agent-team/lib/member-time-context.js +2 -5
- package/packages/agent-team/lib/preset-roster.js +15 -0
- package/packages/agent-team/lib/pressure-policy.js +9 -6
- package/packages/agent-team/lib/session-remediation.js +493 -0
- package/packages/agent-team/lib/stored-session-reader.js +2 -1
- package/packages/agent-team/lib/typert.host.js +605 -291
- package/packages/agent-team/lib/typert.remote-client.d.ts +15 -6
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +367 -244
- package/packages/agent-team/lib/types/context-continuity-host.d.ts +84 -0
- package/packages/agent-team/lib/types/context-continuity-host.d.ts.map +1 -0
- package/packages/agent-team/lib/types/context-projection.d.ts +130 -185
- package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
- package/packages/agent-team/lib/types/context-source.d.ts +17 -81
- package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
- package/packages/agent-team/lib/types/human-avatar.d.ts +29 -0
- package/packages/agent-team/lib/types/human-avatar.d.ts.map +1 -0
- package/packages/agent-team/lib/types/human-profile.d.ts +44 -0
- package/packages/agent-team/lib/types/human-profile.d.ts.map +1 -0
- package/packages/agent-team/lib/types/human-update-check.d.ts +73 -0
- package/packages/agent-team/lib/types/human-update-check.d.ts.map +1 -0
- package/packages/agent-team/lib/types/index.d.ts +112 -38
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +31 -2
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-context.d.ts +0 -14
- package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-time-context.d.ts +0 -15
- package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
- 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/pressure-policy.d.ts +6 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
- package/packages/agent-team/lib/types/session-remediation.d.ts +172 -0
- package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -0
- package/packages/agent-team/lib/types/stored-session-reader.d.ts +2 -1
- package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/entities.d.ts +20 -3
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +63 -0
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +166 -0
- package/packages/client-agent-team/lib/client.js +1667 -516
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts +15 -0
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.js +115 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +12 -8
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +22 -1
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +25 -2
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +6 -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 +69 -31
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +4 -4
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +3 -3
- 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 +12 -5
- package/packages/client-agent-team/lib/types/client/TeamFooterAction.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +4 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +22 -7
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +11 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +69 -8
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +18 -3
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +213 -67
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +5 -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 +55 -29
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +3 -3
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceSelector.js +2 -2
- package/packages/client-agent-team/lib/types/client/avatar-image.d.ts +21 -0
- package/packages/client-agent-team/lib/types/client/avatar-image.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/avatar-image.js +9 -0
- package/packages/client-agent-team/lib/types/client/human-identity.d.ts +94 -0
- package/packages/client-agent-team/lib/types/client/human-identity.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/human-identity.js +117 -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 +112 -41
- package/packages/client-agent-team/lib/types/client/locales.d.ts +72 -2
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +78 -2
- package/packages/client-agent-team/lib/types/client/multi-menu-field.js +2 -2
- package/packages/client-agent-team/lib/types/client/refs.d.ts +71 -0
- package/packages/client-agent-team/lib/types/client/refs.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/refs.js +181 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +8 -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 +57 -9
- 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 +85 -17
- package/packages/tool-agent-team/README.md +1 -1
- package/packages/tool-agent-team/README.zh.md +1 -1
- package/packages/tool-agent-team/lib/context-tools.js +20 -6
- package/packages/tool-agent-team/lib/index.js +2 -2
- package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
- package/packages/agent-team/lib/context-management.js +0 -371
- package/packages/agent-team/lib/types/context-management.d.ts +0 -154
- package/packages/agent-team/lib/types/context-management.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts +0 -31
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/task-refs.js +0 -99
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
+
import { type TeamHumanIdentityFace } from './human-identity.ts';
|
|
3
|
+
export interface HumanSettingsSectionInjected {
|
|
4
|
+
/** The shared Human identity projection: profile facts plus post-write refresh. */
|
|
5
|
+
identity: TeamHumanIdentityFace;
|
|
6
|
+
/** Persist one renamed display name; the failure message when the Host refuses it. */
|
|
7
|
+
saveName: (name: string) => Promise<string | undefined>;
|
|
8
|
+
/** Upload one image file, persist its reference; the failure message when it does not stick. */
|
|
9
|
+
uploadAvatar: (file: File) => Promise<string | undefined>;
|
|
10
|
+
/** Clear the avatar; the identity falls back to the initial. */
|
|
11
|
+
removeAvatar: () => Promise<string | undefined>;
|
|
12
|
+
}
|
|
13
|
+
export type HumanSettingsSectionProps = PropsRuntime<'settings.section'> & PropsLocale<'team'> & HumanSettingsSectionInjected;
|
|
14
|
+
export declare function HumanSettingsSection(props: HumanSettingsSectionProps): import("react").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=HumanSettingsSection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HumanSettingsSection.d.ts","sourceRoot":"","sources":["../../../src/client/HumanSettingsSection.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEjF,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAiBlF,MAAM,WAAW,4BAA4B;IAC3C,mFAAmF;IACnF,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,sFAAsF;IACtF,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACvD,gGAAgG;IAChG,YAAY,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACzD,gEAAgE;IAChE,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CAChD;AAED,MAAM,MAAM,yBAAyB,GACjC,YAAY,CAAC,kBAAkB,CAAC,GAChC,WAAW,CAAC,MAAM,CAAC,GACnB,4BAA4B,CAAA;AAsBhC,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,+BAiKpE"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { Button, Input } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
|
+
import { useHumanIdentity } from "./human-identity.js";
|
|
5
|
+
import { useAvatarImage } from "./avatar-image.js";
|
|
6
|
+
import css from './human-settings.module.css';
|
|
7
|
+
/**
|
|
8
|
+
* The Human's own settings page: display name, avatar, and the version
|
|
9
|
+
* footnote.
|
|
10
|
+
*
|
|
11
|
+
* The page owns no durable fact and no copy of one. Name, avatar, and version
|
|
12
|
+
* all arrive from the shared identity projection, so a save here moves the
|
|
13
|
+
* message rows and member refs at the same moment; a failed write keeps the
|
|
14
|
+
* typed name in the field and reports the Host's own reason.
|
|
15
|
+
*/
|
|
16
|
+
/** Host-side avatar ceiling (`ATTACHMENT_MAX_BYTES`): the settings page refuses larger files before the round trip. */
|
|
17
|
+
const AVATAR_MAX_BYTES = 10 * 1024 * 1024;
|
|
18
|
+
/**
|
|
19
|
+
* Run one durable write and reduce every failure to the message the page
|
|
20
|
+
* reports. A rejected Remote call (a dropped carrier, a refused write) is a
|
|
21
|
+
* failure like any other: without this the field would sit on "saving" forever
|
|
22
|
+
* and report nothing.
|
|
23
|
+
*/
|
|
24
|
+
async function failureOf(action) {
|
|
25
|
+
try {
|
|
26
|
+
return await action();
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
return error instanceof Error ? error.message : String(error);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** First visible character of a display name, or the neutral `H` before one is known. */
|
|
33
|
+
function avatarInitial(name) {
|
|
34
|
+
const trimmed = (name ?? '').replace(/^@/, '').trim();
|
|
35
|
+
return trimmed === '' ? 'H' : trimmed.slice(0, 1).toUpperCase();
|
|
36
|
+
}
|
|
37
|
+
export function HumanSettingsSection(props) {
|
|
38
|
+
const { t, identity } = props;
|
|
39
|
+
const profile = useHumanIdentity(identity);
|
|
40
|
+
// Undefined means "follow the Host value": the field re-syncs whenever the
|
|
41
|
+
// profile changes underneath, without a background read clobbering a name
|
|
42
|
+
// the reader is still editing.
|
|
43
|
+
const [draft, setDraft] = useState(undefined);
|
|
44
|
+
const [saving, setSaving] = useState(false);
|
|
45
|
+
const [uploading, setUploading] = useState(false);
|
|
46
|
+
const [notice, setNotice] = useState(null);
|
|
47
|
+
const filePicker = useRef(null);
|
|
48
|
+
const name = draft ?? profile.name ?? '';
|
|
49
|
+
const dirty = draft !== undefined && draft.trim() !== (profile.name ?? '');
|
|
50
|
+
const emptyName = draft !== undefined && draft.trim() === '';
|
|
51
|
+
const hasAvatar = profile.avatarRef !== undefined;
|
|
52
|
+
// The circle answers to decoded bytes, not to a stored reference: the Host
|
|
53
|
+
// accepts any `image/…` payload, so a file this browser cannot read has to
|
|
54
|
+
// fall back to the initial exactly as a removed one does.
|
|
55
|
+
const avatar = useAvatarImage(profile.avatarUrl);
|
|
56
|
+
const submitName = async () => {
|
|
57
|
+
if (!dirty || emptyName || saving)
|
|
58
|
+
return;
|
|
59
|
+
setSaving(true);
|
|
60
|
+
setNotice(null);
|
|
61
|
+
const failure = await failureOf(() => props.saveName(name.trim()));
|
|
62
|
+
setSaving(false);
|
|
63
|
+
if (failure !== undefined) {
|
|
64
|
+
setNotice(t('humanSettingsNameFailed', { message: failure }));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
setDraft(undefined);
|
|
68
|
+
};
|
|
69
|
+
const pickAvatar = async (file) => {
|
|
70
|
+
if (file === undefined)
|
|
71
|
+
return;
|
|
72
|
+
setNotice(null);
|
|
73
|
+
if (!file.type.startsWith('image/')) {
|
|
74
|
+
setNotice(t('humanSettingsAvatarNotImage'));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (file.size > AVATAR_MAX_BYTES) {
|
|
78
|
+
setNotice(t('humanSettingsAvatarTooLarge'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
setUploading(true);
|
|
82
|
+
const failure = await failureOf(() => props.uploadAvatar(file));
|
|
83
|
+
setUploading(false);
|
|
84
|
+
if (failure !== undefined)
|
|
85
|
+
setNotice(t('humanSettingsAvatarFailed', { message: failure }));
|
|
86
|
+
};
|
|
87
|
+
const removeAvatar = async () => {
|
|
88
|
+
setNotice(null);
|
|
89
|
+
const failure = await failureOf(() => props.removeAvatar());
|
|
90
|
+
if (failure !== undefined)
|
|
91
|
+
setNotice(t('humanSettingsAvatarFailed', { message: failure }));
|
|
92
|
+
};
|
|
93
|
+
// The page says what it is before it says what happened: the settings nav
|
|
94
|
+
// lists it beside the Harness's own pages, so the title alone leaves "whose
|
|
95
|
+
// profile is this, and where does it apply?" unanswered — and every state,
|
|
96
|
+
// including a failed read, has to answer it.
|
|
97
|
+
const pageHeader = _jsxs(_Fragment, { children: [_jsx("h2", { className: css.heading, children: t('humanSettingsTitle') }), _jsx("p", { className: css.intro, children: t('humanSettingsIntro') })] });
|
|
98
|
+
if (profile.status === 'loading' && profile.name === undefined) {
|
|
99
|
+
return _jsxs("div", { className: css.section, children: [pageHeader, _jsx("p", { className: css.state, role: "status", children: t('humanSettingsLoading') })] });
|
|
100
|
+
}
|
|
101
|
+
if (profile.name === undefined) {
|
|
102
|
+
return _jsxs("div", { className: css.section, children: [pageHeader, _jsx("p", { className: css.state, role: "alert", children: t('humanSettingsUnavailable', { message: profile.error ?? '' }) }), _jsx("div", { className: css.stateAction, children: _jsx(Button, { variant: "outline", onClick: () => { void identity.refresh(); }, children: t('retry') }) })] });
|
|
103
|
+
}
|
|
104
|
+
return (_jsxs("div", { className: css.section, children: [pageHeader, _jsxs("div", { className: css.rows, children: [_jsxs("div", { className: css.row, children: [_jsxs("div", { className: css.rowText, children: [_jsx("div", { className: css.title, children: t('humanSettingsName') }), _jsx("div", { className: css.desc, children: t('humanSettingsNameHint') })] }), _jsxs("form", { className: css.controls, onSubmit: (event) => {
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
void submitName();
|
|
107
|
+
}, children: [_jsx(Input, { className: css.nameInput, "aria-label": t('humanSettingsName'), "aria-invalid": emptyName || undefined, value: name, disabled: saving, onChange: (event) => { setDraft(event.target.value); } }), _jsx(Button, { type: "submit", variant: "primary", disabled: saving || !dirty || emptyName, children: saving ? t('humanSettingsSaving') : t('humanSettingsSave') })] })] }), _jsxs("div", { className: css.row, children: [_jsxs("div", { className: css.rowText, children: [_jsx("div", { className: css.title, children: t('humanSettingsAvatar') }), _jsx("div", { className: css.desc, children: t('humanSettingsAvatarHint') })] }), _jsxs("div", { className: css.controls, children: [avatar.src === undefined
|
|
108
|
+
? _jsx("span", { className: `${css.identity} ${css.identityFallback}`, "data-avatar": "initial", "aria-hidden": "true", children: avatarInitial(profile.name) })
|
|
109
|
+
: _jsx("img", { className: `${css.identity} ${css.identityImage}`, "data-avatar": "image", src: avatar.src, alt: "", onError: avatar.failed }), _jsx("input", { ref: filePicker, type: "file", accept: "image/*", tabIndex: -1, "aria-hidden": "true", hidden: true, onChange: (event) => {
|
|
110
|
+
void pickAvatar(event.target.files?.[0]);
|
|
111
|
+
event.target.value = '';
|
|
112
|
+
} }), _jsx(Button, { variant: "outline", disabled: uploading, onClick: () => { filePicker.current?.click(); }, children: uploading ? t('humanSettingsUploading') : hasAvatar ? t('humanSettingsReplace') : t('humanSettingsUpload') }), hasAvatar && _jsx(Button, { disabled: uploading, onClick: () => { void removeAvatar(); }, children: t('humanSettingsRemoveAvatar') })] })] })] }), _jsxs("div", { className: css.footnote, children: [_jsx("span", { children: t('humanSettingsVersion', { version: profile.version ?? '' }) }), _jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsx("a", { className: css.link, href: profile.repoUrl ?? '', target: "_blank", rel: "noreferrer", children: "GitHub" }), profile.updateAvailable && profile.latestVersion !== undefined
|
|
113
|
+
? _jsx("a", { className: css.link, href: `${profile.repoUrl ?? ''}/releases`, target: "_blank", rel: "noreferrer", children: t('humanSettingsUpdateAvailable', { version: profile.latestVersion }) })
|
|
114
|
+
: null] }), emptyName && _jsx("p", { className: css.notice, children: t('humanSettingsNameEmpty') }), notice === null ? null : _jsx("p", { className: css.notice, role: "alert", children: notice })] }));
|
|
115
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamAgentsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAgentsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAE5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAclD,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,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,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAA;IAC3D,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,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+
|
|
1
|
+
{"version":3,"file":"TeamAgentsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAgentsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAE5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAclD,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,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,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAA;IAC3D,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,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BA+O9P"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { Button,
|
|
3
|
+
import { Button, IconArchiveOutline20, IconEditOutline16, IconPlayOutline16, IconPlusOutline16, IconRefreshOutline16, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { TeamMemberIdentity } from "./TeamMemberRow.js";
|
|
5
5
|
import { SortableRow, useSidebarRowDrag } from "./sidebar-drag.js";
|
|
6
6
|
import { moveSidebarItem, useSidebarOrder } from "./sidebar-order.js";
|
|
@@ -9,7 +9,7 @@ import { mintRequestId } from "./requests.js";
|
|
|
9
9
|
import { diagnosticText, restartOffered } from "./TeamPresenceDot.js";
|
|
10
10
|
import { TeamRowMenu } from "./TeamRowMenu.js";
|
|
11
11
|
import { TeamSidebarSection } from "./TeamSidebarSection.js";
|
|
12
|
-
import { AgentEditorDialog, ModelPickerField, sameModel } from "./TeamMemberEditor.js";
|
|
12
|
+
import { AgentEditorDialog, ModelPickerField, sameModel, warmModelCatalog } from "./TeamMemberEditor.js";
|
|
13
13
|
import { TeamAgentImport } from "./TeamAgentImport.js";
|
|
14
14
|
import createCss from './create.module.css';
|
|
15
15
|
import css from './sidebar.module.css';
|
|
@@ -112,6 +112,10 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, ad
|
|
|
112
112
|
setLoading(false);
|
|
113
113
|
}, [loadMembers, workspaceId, followRollover]);
|
|
114
114
|
useEffect(() => { void refresh(); }, [refresh]);
|
|
115
|
+
// Warm the Host model catalog while the roster loads, so the create and
|
|
116
|
+
// edit pickers open with rows instead of paying the first read on open.
|
|
117
|
+
// Mount-scoped (not per refresh): presence wakes must not refetch it.
|
|
118
|
+
useEffect(() => { warmModelCatalog(loadModels); }, [loadModels]);
|
|
115
119
|
useEffect(() => subscribeChanges({ kind: 'workspace', workspaceId }, update => {
|
|
116
120
|
if (update.type === 'failed') {
|
|
117
121
|
setError(update.message);
|
|
@@ -144,7 +148,7 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, ad
|
|
|
144
148
|
try {
|
|
145
149
|
const result = await addMember(request);
|
|
146
150
|
if (result.ok) {
|
|
147
|
-
const committed = { ...result.value.status, workspaceIds:
|
|
151
|
+
const committed = { ...result.value.status, workspaceIds: result.value.workspaceIds };
|
|
148
152
|
setMembers(current => {
|
|
149
153
|
const retained = current.filter(status => status.member.memberId !== committed.member.memberId);
|
|
150
154
|
return committed.member.state === 'inactive' || committed.member.state === 'archived' ? retained : [...retained, committed];
|
|
@@ -196,7 +200,7 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, ad
|
|
|
196
200
|
...(model === undefined ? {} : { model }),
|
|
197
201
|
});
|
|
198
202
|
};
|
|
199
|
-
return (_jsxs("div", { className: css.panel, children: [_jsxs(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') }), !importing && _jsx(Button, { type: "submit", form: "team-agent-create-form", variant: "primary", disabled: creating || handle.trim().length === 0, children: creating ? t('creatingAgent') : t('createAgent') })] }), children: [_jsx(Button, { className: createCss.modeSwitch, variant: "outline", disabled: creating, "aria-expanded": importing, onClick: () => { setImporting(value => !value); setError(undefined); }, children: importing ? t('createAgent') : t('importAgentTitle') }), formOpen && importing ? _jsx(TeamAgentImport, { workspaceId: workspaceId, loadMembers: loadMembers, joinWorkspace: joinWorkspace, onPending: setCreating, onJoined: async () => { await refresh(); setFormOpen(false); queueMicrotask(() => { triggerRef.current?.focus(); }); }, t: t }) : _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 }), formOpen && error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] })] }), _jsxs(TeamSidebarSection, { title: t('agents'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'agents', open); }, actions: (_jsx(Tooltip, { label: t('addAgent'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addAgent'), onClick: () => { setError(undefined); setImporting(false); setFormOpen(true); }, children: _jsx(
|
|
203
|
+
return (_jsxs("div", { className: css.panel, children: [_jsxs(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') }), !importing && _jsx(Button, { type: "submit", form: "team-agent-create-form", variant: "primary", disabled: creating || handle.trim().length === 0, children: creating ? t('creatingAgent') : t('createAgent') })] }), children: [_jsx(Button, { className: createCss.modeSwitch, variant: "outline", disabled: creating, "aria-expanded": importing, onClick: () => { setImporting(value => !value); setError(undefined); }, children: importing ? t('createAgent') : t('importAgentTitle') }), formOpen && importing ? _jsx(TeamAgentImport, { workspaceId: workspaceId, loadMembers: loadMembers, joinWorkspace: joinWorkspace, onPending: setCreating, onJoined: async () => { await refresh(); setFormOpen(false); queueMicrotask(() => { triggerRef.current?.focus(); }); }, t: t }) : _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 }), formOpen && error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] })] }), _jsxs(TeamSidebarSection, { title: t('agents'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'agents', open); }, actions: (_jsx(Tooltip, { label: t('addAgent'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addAgent'), onClick: () => { setError(undefined); setImporting(false); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('loadingAgents') }), !loading && error === undefined && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('emptyAgents') }), _jsx("div", { className: css.agentList, children: orderedMembers.map(status => (_jsx(SortableRow, { drag: drag, orderKey: status.member.memberId, children: _jsx(AgentRow, { workspaceId: workspaceId, leaveWorkspace: leaveWorkspace, status: status, ...(memberSessionId === undefined ? {} : { current: status.member.sessionId === memberSessionId }), updateMember: updateMember, recoverMember: recoverMember, archiveMember: archiveMember, loadModels: loadModels, openMemberSession: openMemberSession, onUpdated: 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') })] }))] }));
|
|
200
204
|
}
|
|
201
205
|
/**
|
|
202
206
|
* One sidebar Agent row: the select button opens the Member's own Session
|
|
@@ -267,10 +271,10 @@ function AgentRow({ workspaceId, leaveWorkspace, status, current, updateMember,
|
|
|
267
271
|
}
|
|
268
272
|
};
|
|
269
273
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.agentRow, "data-agent-row": true, "data-menu-open": menuOpen || undefined, children: [_jsx("button", { type: "button", className: css.agentSelect, "aria-label": t('openAgentSession', { name: status.member.handle }), "aria-current": current ? 'page' : undefined, disabled: status.availability !== 'active', onClick: () => { openMemberSession(status.member.sessionId); }, children: _jsx(TeamMemberIdentity, { status: status, name: status.member.handle.replace(/^@/, ''), className: css.agentCopy, t: t }) }), _jsx("span", { className: css.rowMenu, children: _jsx(TeamRowMenu, { label: t('actionsAgent', { name: status.member.handle }), items: [
|
|
270
|
-
{ id: 'edit', label: t('editAgent'), icon: _jsx(
|
|
271
|
-
...(status.presence === 'error' ? [{ id: 'resume', label: t('resumeAgent'), icon: _jsx(
|
|
272
|
-
...(status.availability === 'unavailable' && restartOffered(status) ? [{ id: 'restart', label: t('restartAgent'), icon: _jsx(
|
|
273
|
-
{ id: 'archive', label: t(withdrawing ? 'withdrawAgent' : 'archiveAgent'), icon: _jsx(
|
|
274
|
+
{ id: 'edit', label: t('editAgent'), icon: _jsx(IconEditOutline16, {}) },
|
|
275
|
+
...(status.presence === 'error' ? [{ id: 'resume', label: t('resumeAgent'), icon: _jsx(IconPlayOutline16, {}) }] : []),
|
|
276
|
+
...(status.availability === 'unavailable' && restartOffered(status) ? [{ id: 'restart', label: t('restartAgent'), icon: _jsx(IconRefreshOutline16, {}) }] : []),
|
|
277
|
+
{ id: 'archive', label: t(withdrawing ? 'withdrawAgent' : 'archiveAgent'), icon: _jsx(IconArchiveOutline20, { size: 16 }), danger: true },
|
|
274
278
|
], onSelect: (id) => {
|
|
275
279
|
if (id === 'edit')
|
|
276
280
|
setEditing(true);
|
|
@@ -5,14 +5,35 @@ export interface TeamAvatarOwner {
|
|
|
5
5
|
/** Public handle, or the raw Member id when the roster no longer names them. */
|
|
6
6
|
readonly name: string;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* The reader's own identity, as the Client's one Human projection holds it.
|
|
10
|
+
*
|
|
11
|
+
* The stack draws every owner from the shared Member language — hue plus
|
|
12
|
+
* initial — which is the whole identity an Agent has. The Human is the one
|
|
13
|
+
* Member with a picture, so that one chip draws the picture and falls back to
|
|
14
|
+
* this name's initial, exactly as the timeline already does.
|
|
15
|
+
*/
|
|
16
|
+
export interface TeamAvatarHuman {
|
|
17
|
+
readonly memberId: AgentTeamMemberId;
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly avatarUrl?: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Owners as a seat names them: the Client's Human identity outranks the name
|
|
23
|
+
* the Host projected for that actor, so a rename moves label and initial
|
|
24
|
+
* together in every seat that draws the stack.
|
|
25
|
+
*/
|
|
26
|
+
export declare function namedAvatarOwners(owners: readonly TeamAvatarOwner[], human: TeamAvatarHuman | undefined): readonly TeamAvatarOwner[];
|
|
8
27
|
/**
|
|
9
28
|
* The compact "who is on this work" stack: overlapping 18px Member circles in
|
|
10
29
|
* the shared identity language, capped at three plus a `+N` chip. The circles
|
|
11
30
|
* are presentational, so the stack is one `role="img"` whose label carries the
|
|
12
31
|
* whole roster — three anonymous initials would read as noise.
|
|
13
32
|
*/
|
|
14
|
-
export declare function TeamAvatarStack({ owners, label }: {
|
|
33
|
+
export declare function TeamAvatarStack({ owners, label, human }: {
|
|
15
34
|
readonly owners: readonly TeamAvatarOwner[];
|
|
16
35
|
readonly label: string;
|
|
36
|
+
/** The reader's own identity; one owner matching it draws the Human's picture. */
|
|
37
|
+
readonly human?: TeamAvatarHuman | undefined;
|
|
17
38
|
}): import("react").JSX.Element | null;
|
|
18
39
|
//# sourceMappingURL=TeamAvatarStack.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamAvatarStack.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAvatarStack.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;
|
|
1
|
+
{"version":3,"file":"TeamAvatarStack.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAvatarStack.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAQvE,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACxC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,EAAE,KAAK,EAAE,eAAe,GAAG,SAAS,GAAG,SAAS,eAAe,EAAE,CAGpI;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;IACxD,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAA;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,kFAAkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;CAC7C,sCAQA"}
|
|
@@ -1,20 +1,43 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useAvatarImage } from "./avatar-image.js";
|
|
2
3
|
import { memberHue } from "./team-formatters.js";
|
|
3
4
|
import css from './avatar-stack.module.css';
|
|
4
5
|
/** Distinct owners past this count collapse into one `+N` chip. */
|
|
5
6
|
const MAX_VISIBLE = 3;
|
|
7
|
+
/**
|
|
8
|
+
* Owners as a seat names them: the Client's Human identity outranks the name
|
|
9
|
+
* the Host projected for that actor, so a rename moves label and initial
|
|
10
|
+
* together in every seat that draws the stack.
|
|
11
|
+
*/
|
|
12
|
+
export function namedAvatarOwners(owners, human) {
|
|
13
|
+
if (human === undefined)
|
|
14
|
+
return owners;
|
|
15
|
+
return owners.map(owner => owner.memberId === human.memberId ? { memberId: owner.memberId, name: human.name } : owner);
|
|
16
|
+
}
|
|
6
17
|
/**
|
|
7
18
|
* The compact "who is on this work" stack: overlapping 18px Member circles in
|
|
8
19
|
* the shared identity language, capped at three plus a `+N` chip. The circles
|
|
9
20
|
* are presentational, so the stack is one `role="img"` whose label carries the
|
|
10
21
|
* whole roster — three anonymous initials would read as noise.
|
|
11
22
|
*/
|
|
12
|
-
export function TeamAvatarStack({ owners, label }) {
|
|
23
|
+
export function TeamAvatarStack({ owners, label, human }) {
|
|
13
24
|
if (owners.length === 0)
|
|
14
25
|
return null;
|
|
15
26
|
const shown = owners.slice(0, MAX_VISIBLE);
|
|
16
27
|
const overflow = owners.length - shown.length;
|
|
17
|
-
return _jsxs("span", { className: css.stack, role: "img", "aria-label": label, children: [shown.map(owner => _jsx(
|
|
28
|
+
return _jsxs("span", { className: css.stack, role: "img", "aria-label": label, children: [shown.map(owner => _jsx(OwnerAvatar, { owner: owner, human: human }, owner.memberId)), overflow > 0 && _jsx("span", { className: css.overflow, children: `+${overflow}` })] });
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One circle. Only an owner the seat identifies as the Human consults an image
|
|
32
|
+
* at all, and those bytes keep the initial whenever they do not decode — the
|
|
33
|
+
* same promise, and the same hook, the timeline avatar makes.
|
|
34
|
+
*/
|
|
35
|
+
function OwnerAvatar({ owner, human }) {
|
|
36
|
+
const image = useAvatarImage(human !== undefined && owner.memberId === human.memberId ? human.avatarUrl : undefined);
|
|
37
|
+
const hue = { '--team-avatar-hue': memberHue(owner.memberId) };
|
|
38
|
+
if (image.src === undefined)
|
|
39
|
+
return _jsx("span", { className: css.avatar, style: hue, children: initial(owner.name) });
|
|
40
|
+
return _jsx("img", { className: css.avatarImage, style: hue, src: image.src, alt: "", "aria-hidden": "true", onError: image.failed });
|
|
18
41
|
}
|
|
19
42
|
/** First visible character of a handle — and of a raw Member id when that is all there is. */
|
|
20
43
|
function initial(name) {
|
|
@@ -5,6 +5,9 @@ import type { TeamDraftStore } from './drafts.ts';
|
|
|
5
5
|
interface TeamChannelPageProps {
|
|
6
6
|
readonly workspaceId: WorkspaceId;
|
|
7
7
|
readonly channelRef: AgentTeamChannelRef;
|
|
8
|
+
/** The Human's own display name and avatar, from the shared identity projection. */
|
|
9
|
+
readonly humanName: string;
|
|
10
|
+
readonly humanAvatarUrl?: string | undefined;
|
|
8
11
|
readonly loadChannels: TeamConversationProps['loadChannels'];
|
|
9
12
|
readonly subscribeChanges: TeamConversationProps['subscribeChanges'];
|
|
10
13
|
readonly loadMembers: TeamConversationProps['loadMembers'];
|
|
@@ -19,9 +22,11 @@ interface TeamChannelPageProps {
|
|
|
19
22
|
readonly selectThread: TeamConversationProps['selectThread'];
|
|
20
23
|
readonly selectChannel: TeamConversationProps['selectChannel'];
|
|
21
24
|
readonly resolveTaskRefs: TeamConversationProps['resolveTaskRefs'];
|
|
25
|
+
readonly resolveThreadRefs: TeamConversationProps['resolveThreadRefs'];
|
|
26
|
+
readonly openMemberSession: TeamConversationProps['openMemberSession'];
|
|
22
27
|
readonly backToChannels: TeamConversationProps['backToChannels'];
|
|
23
28
|
readonly t: TeamConversationProps['t'];
|
|
24
29
|
}
|
|
25
|
-
export declare function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, loadInbox, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, selectChannel, backToChannels, resolveTaskRefs, t }: TeamChannelPageProps): import("react").JSX.Element;
|
|
30
|
+
export declare function TeamChannelPage({ workspaceId, channelRef, humanName, humanAvatarUrl, loadChannels, subscribeChanges, loadMembers, loadInbox, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, selectChannel, backToChannels, resolveTaskRefs, resolveThreadRefs, openMemberSession, t }: TeamChannelPageProps): import("react").JSX.Element;
|
|
26
31
|
export {};
|
|
27
32
|
//# 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,EAE7D,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"TeamChannelPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA+B,mBAAmB,EAE7D,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAmB/D,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAA;IACxC,oFAAoF;IACpF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5C,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,iGAAiG;IACjG,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC,WAAW,CAAC,CAAA;IACtD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,mBAAmB,EAAE,qBAAqB,CAAC,qBAAqB,CAAC,CAAA;IAC1E,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAA;IACtE,QAAQ,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAA;IACtE,QAAQ,CAAC,cAAc,EAAE,qBAAqB,CAAC,gBAAgB,CAAC,CAAA;IAChE,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AAkDD,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BA4Y/V"}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Fragment, useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
-
import { Button,
|
|
2
|
+
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Button, IconChevronLeftOutline14, IconChevronRightOutline14, Modal } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { mintRequestId, uploadComposerFiles } from "./requests.js";
|
|
5
5
|
import { TeamComposer } from "./TeamComposer.js";
|
|
6
6
|
import { TeamMemberRow } from "./TeamMemberRow.js";
|
|
7
7
|
import { TeamMessage } from "./TeamMessage.js";
|
|
8
|
-
import { TeamAvatarStack } from "./TeamAvatarStack.js";
|
|
8
|
+
import { namedAvatarOwners, TeamAvatarStack } from "./TeamAvatarStack.js";
|
|
9
9
|
import { TeamCountBadge } from "./TeamCountBadge.js";
|
|
10
10
|
import { TeamRunDivider } from "./TeamRunDivider.js";
|
|
11
11
|
import { claimersLabel, formatAbsoluteTime, formatInboxTime, formatTaskStatus, taskStatusDot, mentionNamesOf } from "./team-formatters.js";
|
|
12
12
|
import { TeamStateDot } from "./TeamStateDot.js";
|
|
13
13
|
import { useChannelMembership } from "./team-membership.js";
|
|
14
14
|
import { useTimelineScroll } from "./timeline-scroll.js";
|
|
15
|
-
import { hostTaskRefLookup, jumpToTaskThread } from "./
|
|
15
|
+
import { hostTaskRefLookup, jumpToTaskThread } from "./refs.js";
|
|
16
|
+
import { hostThreadRefLookup, jumpToThread } from "./refs.js";
|
|
17
|
+
import { rosterChannelName, rosterMember } from "./refs.js";
|
|
16
18
|
import { chunkRunsWithDays, isRunGap } from "./team-separators.js";
|
|
17
19
|
import channelCss from './channel.module.css';
|
|
18
20
|
import css from './conversation.module.css';
|
|
@@ -44,11 +46,24 @@ function followUpAt(item) {
|
|
|
44
46
|
const lastActivityAt = item.lastActivityAt;
|
|
45
47
|
return lastActivityAt === undefined || lastActivityAt === item.message.occurredAt ? undefined : lastActivityAt;
|
|
46
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* What the door prints about follow-up activity, or nothing. A resolved Task
|
|
51
|
+
* stops printing it: the status word beside the door already says nothing is
|
|
52
|
+
* moving, and the instant it would print is the moment it resolved — repeated
|
|
53
|
+
* on every finished row. The precise instant stays on the control's title, one
|
|
54
|
+
* hover away. A taskless Thread never resolves, so a discussion keeps the
|
|
55
|
+
* recency that is the whole of its state line: it wears no status word, dot, or
|
|
56
|
+
* owner stack.
|
|
57
|
+
*/
|
|
58
|
+
function printedActivityAt(item) {
|
|
59
|
+
const status = item.task?.status;
|
|
60
|
+
return status === 'done' || status === 'closed' ? undefined : followUpAt(item);
|
|
61
|
+
}
|
|
47
62
|
/** Host unread per Thread, keyed for the feed's rows: zero unread is the absence of a badge, not a row. */
|
|
48
63
|
function unreadCounts(inbox) {
|
|
49
64
|
return new Map(inbox.items.filter(item => item.unreadCount > 0).map(item => [item.thread.threadRef, item.unreadCount]));
|
|
50
65
|
}
|
|
51
|
-
export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, loadInbox, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, selectChannel, backToChannels, resolveTaskRefs, t }) {
|
|
66
|
+
export function TeamChannelPage({ workspaceId, channelRef, humanName, humanAvatarUrl, loadChannels, subscribeChanges, loadMembers, loadInbox, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, selectChannel, backToChannels, resolveTaskRefs, resolveThreadRefs, openMemberSession, t }) {
|
|
52
67
|
const [view, setView] = useState();
|
|
53
68
|
const [members, setMembers] = useState([]);
|
|
54
69
|
const [unreadByThread, setUnreadByThread] = useState(new Map());
|
|
@@ -76,8 +91,10 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
76
91
|
const refreshSequenceRef = useRef(0);
|
|
77
92
|
const channelLastItem = view?.items[view.items.length - 1];
|
|
78
93
|
// Branded-ref navigation for message bodies: channel refs hop directly,
|
|
79
|
-
// task refs resolve against this Channel's loaded timeline and
|
|
80
|
-
//
|
|
94
|
+
// task and thread refs resolve against this Channel's loaded timeline and
|
|
95
|
+
// fall back to the Host resolver, which also accepts abbreviated spellings.
|
|
96
|
+
// Unresolvable refs never become links (see TeamMessage), so this path only
|
|
97
|
+
// fires for refs the Host already confirmed.
|
|
81
98
|
const openRef = (ref) => {
|
|
82
99
|
if (ref.startsWith('channel:')) {
|
|
83
100
|
if (ref !== channelRef)
|
|
@@ -90,18 +107,9 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
90
107
|
selectThread(match.thread.threadRef, channelRef, match.task?.taskRef, match.taskNumber);
|
|
91
108
|
return;
|
|
92
109
|
}
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
if (!result.ok)
|
|
97
|
-
return;
|
|
98
|
-
const target = result.value.items[0];
|
|
99
|
-
if (target !== undefined) {
|
|
100
|
-
const targetChannel = result.value.channels.find(channel => channel.channelRef === target.message.channelRef);
|
|
101
|
-
if (targetChannel !== undefined)
|
|
102
|
-
selectThread(target.thread.threadRef, targetChannel.channelRef, target.task?.taskRef, target.taskNumber);
|
|
103
|
-
}
|
|
104
|
-
});
|
|
110
|
+
// Not in the loaded timeline (or cited by abbreviation): resolve through
|
|
111
|
+
// the Host and jump to the Thread's home Channel.
|
|
112
|
+
jumpToThread(resolveThreadRefs, workspaceId, ref, selectThread);
|
|
105
113
|
return;
|
|
106
114
|
}
|
|
107
115
|
if (ref.startsWith('task:')) {
|
|
@@ -116,6 +124,33 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
116
124
|
}
|
|
117
125
|
};
|
|
118
126
|
const lookupTaskRefs = hostTaskRefLookup(resolveTaskRefs, workspaceId);
|
|
127
|
+
const lookupThreadRefs = hostThreadRefLookup(resolveThreadRefs, workspaceId);
|
|
128
|
+
// Roster chips resolve synchronously from loaded data: channel names from
|
|
129
|
+
// the loaded feed windows, member facts from the member list. Anything
|
|
130
|
+
// outside stays plain text — the same rule unresolvable refs follow. The
|
|
131
|
+
// lookups key on roster content, not array identity: every refresh hands
|
|
132
|
+
// over fresh arrays, and the memoized rows must survive change bursts that
|
|
133
|
+
// leave the roster itself untouched.
|
|
134
|
+
const rosterKey = useMemo(() => [
|
|
135
|
+
(view?.channels ?? []).map(channel => `${channel.channelRef}=${channel.name}=${channel.state}`).join(','),
|
|
136
|
+
members.map(status => `${status.member.memberId}=${status.member.handle}=${status.member.sessionId}=${status.availability}`).join(','),
|
|
137
|
+
view?.humanMemberId ?? '',
|
|
138
|
+
].join(';'), [view, members]);
|
|
139
|
+
const channelNameOf = useMemo(() => {
|
|
140
|
+
const channels = view?.channels ?? [];
|
|
141
|
+
return (ref) => rosterChannelName(channels, ref);
|
|
142
|
+
}, [rosterKey]);
|
|
143
|
+
const memberOf = useMemo(() => {
|
|
144
|
+
const humanMemberId = view?.humanMemberId;
|
|
145
|
+
return (ref) => rosterMember(members, humanMemberId, humanName, ref);
|
|
146
|
+
}, [rosterKey, humanName]);
|
|
147
|
+
// The Thread entry row's owner stack speaks the same grammar as the Inbox
|
|
148
|
+
// row's, Human included: the Host names which actor is the reader, the
|
|
149
|
+
// Client's identity projection says what that actor looks like.
|
|
150
|
+
const humanMemberId = view?.humanMemberId;
|
|
151
|
+
const humanIdentity = humanMemberId === undefined
|
|
152
|
+
? undefined
|
|
153
|
+
: { memberId: humanMemberId, name: humanName, ...(humanAvatarUrl === undefined ? {} : { avatarUrl: humanAvatarUrl }) };
|
|
119
154
|
const timeline = useTimelineScroll(`${view?.items.length ?? 0}:${channelLastItem?.message.messageRef ?? ''}`);
|
|
120
155
|
const channel = view?.channels.find(item => item.channelRef === channelRef);
|
|
121
156
|
const channelMemberIds = new Set(view?.members.filter(item => item.channelRef === channelRef).map(item => item.memberId) ?? []);
|
|
@@ -346,7 +381,7 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
346
381
|
setPending(false);
|
|
347
382
|
}
|
|
348
383
|
};
|
|
349
|
-
return _jsxs("main", { className: css.surface, "data-team-channel": channelRef, children: [_jsxs("div", { className: css.surfaceHeader, children: [_jsx("div", { className: css.backRow, children: _jsx(Button, { size: "sm", icon: _jsx(
|
|
384
|
+
return _jsxs("main", { className: css.surface, "data-team-channel": channelRef, children: [_jsxs("div", { className: css.surfaceHeader, children: [_jsx("div", { className: css.backRow, children: _jsx(Button, { size: "sm", icon: _jsx(IconChevronLeftOutline14, {}), onClick: backToChannels, children: t('backToChannels') }) }), _jsxs("header", { className: css.headerRow, children: [_jsxs("div", { className: css.headerCopy, children: [_jsx("h1", { children: channel === undefined ? '# …' : `# ${channel.name}` }), channel !== undefined && _jsx("p", { children: channel.description }), channel !== undefined && _jsxs("div", { className: channelCss.headerMeta, children: [_jsx("span", { children: t('memberCount', { count: channelMembers.length }) }), _jsx("span", { children: t('onlineCount', { count: onlineCount }) })] })] }), channel !== undefined && _jsx("span", { ref: manageTriggerRef, children: _jsx(Button, { size: "sm", variant: "outline", "aria-haspopup": "dialog", onClick: () => { setManagingMembers(true); }, children: t('manageMembers') }) })] })] }), _jsx(Modal, { open: managingMembers, onClose: closeMembers, title: t('channelMembers'), ...(channel === undefined ? {} : { description: `# ${channel.name} · ${t('memberCount', { count: channelMembers.length })}` }), closeLabel: t('close'), contentClassName: channelCss.modalBody, children: _jsx("div", { ref: memberListRef, className: channelCss.memberList, children: members.filter(status => status.member.state !== 'inactive' && status.member.state !== 'archived').map(status => {
|
|
350
385
|
const joined = channelMemberIds.has(status.member.memberId);
|
|
351
386
|
const rowPending = membership.pending.has(status.member.memberId);
|
|
352
387
|
// Joining needs an active Member — the Host refuses any other
|
|
@@ -364,7 +399,7 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
364
399
|
: _jsx("div", { className: css.messageRun, children: block.items.map((item, index) => {
|
|
365
400
|
const senderStatus = members.find(member => member.member.memberId === item.message.sender);
|
|
366
401
|
const human = item.message.sender === view.humanMemberId;
|
|
367
|
-
const sender = human ?
|
|
402
|
+
const sender = human ? humanName : senderStatus?.member.handle ?? item.message.sender;
|
|
368
403
|
const turnGap = isRunGap(index > 0 ? block.items[index - 1].message.occurredAt : undefined, item.message.occurredAt);
|
|
369
404
|
const task = item.task;
|
|
370
405
|
// The entry's own line is the gate under the body, and its state
|
|
@@ -373,7 +408,7 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
373
408
|
// of it — the Thread page owns that surface.
|
|
374
409
|
const unread = unreadByThread.get(item.thread.threadRef) ?? 0;
|
|
375
410
|
// A turn divider already labels the gapped entry; its row stays chrome-free.
|
|
376
|
-
return _jsxs(Fragment, { children: [turnGap && _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, handleByMember), onOpenRef: openRef, onResolveTaskRefs: lookupTaskRefs, grouped: index > 0, showGroupedTime: item.message.topLevel === true && !turnGap, ...(senderStatus === undefined ? {} : { senderTitle: senderStatus.member.description }), children: item.message.topLevel && _jsx(ThreadEntryRow, { item: item, owners: item.claimOwners, unread: unread, t: t, onOpen: () => { selectThread(item.thread.threadRef, channelRef, task?.taskRef, item.taskNumber); } }) })] }, item.message.messageRef);
|
|
411
|
+
return _jsxs(Fragment, { children: [turnGap && _jsx(TeamRunDivider, { occurredAt: item.message.occurredAt }), _jsx(TeamMessage, { senderName: sender, memberId: item.message.sender, human: human, ...(humanAvatarUrl === undefined ? {} : { avatarUrl: humanAvatarUrl }), body: item.message.body, attachments: item.message.attachments, loadAttachment: getAttachment, t: t, occurredAt: item.message.occurredAt, mentionNames: mentionNamesOf(item.mentions, handleByMember, humanName), onOpenRef: openRef, onResolveTaskRefs: lookupTaskRefs, onResolveThreadRefs: lookupThreadRefs, channelNameOf: channelNameOf, memberOf: memberOf, onOpenMemberSession: openMemberSession, grouped: index > 0, showGroupedTime: item.message.topLevel === true && !turnGap, ...(senderStatus === undefined ? {} : { senderTitle: senderStatus.member.description }), children: item.message.topLevel && _jsx(ThreadEntryRow, { item: item, owners: item.claimOwners, unread: unread, human: humanIdentity, t: t, onOpen: () => { selectThread(item.thread.threadRef, channelRef, task?.taskRef, item.taskNumber); } }) })] }, item.message.messageRef);
|
|
377
412
|
}) }, `run-${block.items[0].message.messageRef}`))] }) }), channel !== undefined ? _jsx(TeamComposer, { members: channelMembers, drafts: drafts, draftKey: draftKey, pending: pending, ...(statusMessage === undefined ? {} : { confirmation: statusMessage }), ...(error === undefined ? {} : { error }), onEdit: clearSendState, onSubmit: () => { void send(); }, pendingFiles: pendingFiles, onFilesChange: setPendingFiles, asTask: asTask, onAsTaskChange: setAsTask, t: t }, draftKey) : _jsx("div", {})] });
|
|
378
413
|
}
|
|
379
414
|
/**
|
|
@@ -385,27 +420,30 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
|
|
|
385
420
|
* carried nothing else. The unread capsule is the only member a taskless
|
|
386
421
|
* discussion can carry: it needs no Task.
|
|
387
422
|
*/
|
|
388
|
-
function ThreadStateCluster({ task, owners, unread, t }) {
|
|
423
|
+
function ThreadStateCluster({ task, owners, unread, human, t }) {
|
|
389
424
|
if (task === undefined && unread === 0)
|
|
390
425
|
return null;
|
|
391
|
-
|
|
426
|
+
const namedOwners = namedAvatarOwners(owners, human);
|
|
427
|
+
return _jsxs("span", { className: css.stateCluster, children: [task !== undefined && _jsx(TeamAvatarStack, { owners: namedOwners, label: claimersLabel(namedOwners, t), human: human }), task !== undefined && _jsx(TeamStateDot, { size: 8, state: taskStatusDot(task.status) }), task !== undefined && _jsx("span", { className: css.statusWord, children: formatTaskStatus(task.status, t) }), _jsx(TeamCountBadge, { count: unread })] });
|
|
392
428
|
}
|
|
393
429
|
/**
|
|
394
430
|
* The gate under one top-level Message: the single row that opens its Thread,
|
|
395
431
|
* and the one line that says what stands there. State leads it, then what the
|
|
396
432
|
* entry is: a Task entry keeps its number so the status word never floats free
|
|
397
|
-
* of the Task it describes, follow-up activity adds when the work last moved
|
|
398
|
-
* and an unanswered Thread says only 回复.
|
|
399
|
-
* 今天/昨天 form with the precise local time on
|
|
400
|
-
* unread Thread says so in the control's label
|
|
433
|
+
* of the Task it describes, follow-up activity adds when the work last moved —
|
|
434
|
+
* while the work is still unfinished — and an unanswered Thread says only 回复.
|
|
435
|
+
* The instant uses the Inbox's own 今天/昨天 form with the precise local time on
|
|
436
|
+
* the control's title, and an unread Thread says so in the control's label
|
|
437
|
+
* rather than only in pixels.
|
|
401
438
|
*/
|
|
402
|
-
function ThreadEntryRow({ item, owners, unread, t, onOpen }) {
|
|
439
|
+
function ThreadEntryRow({ item, owners, unread, human, t, onOpen }) {
|
|
403
440
|
const taskNumber = item.taskNumber;
|
|
404
441
|
const followUp = followUpAt(item);
|
|
442
|
+
const printedActivity = printedActivityAt(item);
|
|
405
443
|
const label = taskNumber !== undefined
|
|
406
444
|
? t('taskLabel', { number: taskNumber })
|
|
407
445
|
: followUp === undefined ? t('replyAction') : t('threadLabel');
|
|
408
|
-
const text =
|
|
446
|
+
const text = printedActivity === undefined ? label : `${label} · ${t('recentActivity', { time: formatInboxTime(printedActivity, t) })}`;
|
|
409
447
|
const openLabel = taskNumber === undefined
|
|
410
448
|
? unread > 0 ? t('openThreadUnread', { count: unread }) : t('openThread')
|
|
411
449
|
: unread > 0 ? t('openTaskUnread', { number: taskNumber, count: unread }) : t('openTask', { number: taskNumber });
|
|
@@ -413,5 +451,5 @@ function ThreadEntryRow({ item, owners, unread, t, onOpen }) {
|
|
|
413
451
|
// owner stack keeps its own accessible name — a labeled button prunes its
|
|
414
452
|
// descendants from the accessibility tree — and so the control's name says
|
|
415
453
|
// exactly what clicking it does.
|
|
416
|
-
return _jsxs("span", { className: css.entryLine, "data-thread-entry": "", children: [_jsx(ThreadStateCluster, { task: item.task, owners: owners, unread: unread, t: t }), _jsxs("button", { type: "button", className: css.entryRow, "aria-label": openLabel, ...(followUp === undefined ? {} : { title: formatAbsoluteTime(followUp) }), onClick: onOpen, children: [_jsx("span", { children: text }), _jsx("span", { className: css.entryArrow, "aria-hidden": "true", children: _jsx(
|
|
454
|
+
return _jsxs("span", { className: css.entryLine, "data-thread-entry": "", children: [_jsx(ThreadStateCluster, { task: item.task, owners: owners, unread: unread, human: human, t: t }), _jsxs("button", { type: "button", className: css.entryRow, "aria-label": openLabel, ...(followUp === undefined ? {} : { title: formatAbsoluteTime(followUp) }), onClick: onOpen, children: [_jsx("span", { children: text }), _jsx("span", { className: css.entryArrow, "aria-hidden": "true", children: _jsx(IconChevronRightOutline14, { size: 12 }) })] })] });
|
|
417
455
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { Button,
|
|
3
|
+
import { Button, IconArchiveOutline20, IconEditOutline16, IconPlusOutline16, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { TeamPresenceDot } from "./TeamPresenceDot.js";
|
|
5
5
|
import { TeamMemberRow } from "./TeamMemberRow.js";
|
|
6
6
|
import { MultiMenuField } from "./multi-menu-field.js";
|
|
@@ -153,7 +153,7 @@ export function TeamChannelsPanel(props) {
|
|
|
153
153
|
return next;
|
|
154
154
|
});
|
|
155
155
|
setPendingCreate(undefined);
|
|
156
|
-
}, triggerEmptyLabel: t('membersPickerEmpty'), formatCount: count => t('membersPickerCount', { count }) }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('channels'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'channels', open); }, actions: (_jsx(Tooltip, { label: t('addChannel'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addChannel'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(
|
|
156
|
+
}, triggerEmptyLabel: t('membersPickerEmpty'), formatCount: count => t('membersPickerCount', { count }) }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('channels'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'channels', open); }, actions: (_jsx(Tooltip, { label: t('addChannel'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addChannel'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && view === undefined && _jsx("p", { className: css.emptyState, children: t('loadingChannels') }), !loading && error === undefined && view !== undefined && view.channels.length === 0 && _jsx("p", { className: css.emptyState, children: t('emptyChannels') }), _jsx("div", { className: css.channelList, children: orderedChannels.map(channel => {
|
|
157
157
|
const joined = membership.get(channel.channelRef) ?? new Set();
|
|
158
158
|
return (_jsx(SortableRow, { drag: drag, orderKey: channel.channelRef, children: _jsx(ChannelRow, { channel: channel, members: members, joinedIds: joined, selected: selectedChannelRef === channel.channelRef, updateChannel: updateChannel, archiveChannel: props.archiveChannel, joinChannel: props.joinChannel, removeChannelMember: props.removeChannelMember, onCommitted: () => { void refresh(); }, selectChannel: selectChannel, t: t }) }, channel.channelRef));
|
|
159
159
|
}) })] }), !formOpen && error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] }));
|
|
@@ -184,8 +184,8 @@ function ChannelRow({ channel, members, joinedIds, selected, updateChannel, arch
|
|
|
184
184
|
}
|
|
185
185
|
};
|
|
186
186
|
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: [
|
|
187
|
-
{ id: 'edit', label: t('editChannel'), icon: _jsx(
|
|
188
|
-
{ id: 'archive', label: t('archiveChannel'), icon: _jsx(
|
|
187
|
+
{ id: 'edit', label: t('editChannel'), icon: _jsx(IconEditOutline16, {}) },
|
|
188
|
+
{ id: 'archive', label: t('archiveChannel'), icon: _jsx(IconArchiveOutline20, { size: 16 }), danger: true },
|
|
189
189
|
], onSelect: (id) => {
|
|
190
190
|
if (id === 'edit')
|
|
191
191
|
setEditing(true);
|