@wowyuarm/dsh-agent-team 0.1.0 → 0.1.2

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.
Files changed (91) hide show
  1. package/README.md +14 -2
  2. package/README.zh.md +10 -2
  3. package/package.json +32 -24
  4. package/packages/agent-team/lib/attachments.js +157 -0
  5. package/packages/agent-team/lib/index.js +280 -22
  6. package/packages/agent-team/lib/ledger.js +98 -22
  7. package/packages/agent-team/lib/recovery.js +87 -0
  8. package/packages/agent-team/lib/spec.js +27 -5
  9. package/packages/agent-team/lib/typert.host.js +313 -26
  10. package/packages/agent-team/lib/typert.remote-client.d.ts +9 -1
  11. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  12. package/packages/agent-team/lib/typert.remote-client.js +234 -17
  13. package/packages/agent-team/lib/types/attachments.d.ts +69 -0
  14. package/packages/agent-team/lib/types/attachments.d.ts.map +1 -0
  15. package/packages/agent-team/lib/types/index.d.ts +68 -6
  16. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  17. package/packages/agent-team/lib/types/ledger.d.ts +17 -1
  18. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  19. package/packages/agent-team/lib/types/recovery.d.ts +42 -0
  20. package/packages/agent-team/lib/types/recovery.d.ts.map +1 -0
  21. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  22. package/packages/agent-team/lib/types/types.d.ts +94 -3
  23. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  24. package/packages/agent-team/preset/team-member/agent.cordis.yml +2 -2
  25. package/packages/client-agent-team/README.md +4 -3
  26. package/packages/client-agent-team/README.zh.md +4 -3
  27. package/packages/client-agent-team/lib/client.js +1791 -322
  28. package/packages/client-agent-team/lib/client.js.map +1 -1
  29. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +5 -2
  30. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  31. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +108 -59
  32. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +7 -1
  33. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  34. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +80 -19
  35. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  36. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +38 -19
  37. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +4 -1
  38. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  39. package/packages/client-agent-team/lib/types/client/TeamComposer.js +61 -4
  40. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  41. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  42. package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
  43. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +12 -2
  44. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  45. package/packages/client-agent-team/lib/types/client/TeamMessage.js +152 -14
  46. package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts +10 -0
  47. package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts.map +1 -0
  48. package/packages/client-agent-team/lib/types/client/TeamRunDivider.js +12 -0
  49. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +7 -0
  50. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  51. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +93 -16
  52. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  53. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  54. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +6 -5
  55. package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts +22 -0
  56. package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts.map +1 -0
  57. package/packages/client-agent-team/lib/types/client/attachment-preview.js +35 -0
  58. package/packages/client-agent-team/lib/types/client/drafts.d.ts +41 -0
  59. package/packages/client-agent-team/lib/types/client/drafts.d.ts.map +1 -0
  60. package/packages/client-agent-team/lib/types/client/drafts.js +144 -0
  61. package/packages/client-agent-team/lib/types/client/index.d.ts +2 -0
  62. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  63. package/packages/client-agent-team/lib/types/client/index.js +50 -10
  64. package/packages/client-agent-team/lib/types/client/locales.d.ts +34 -0
  65. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  66. package/packages/client-agent-team/lib/types/client/locales.js +34 -0
  67. package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts +33 -0
  68. package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts.map +1 -0
  69. package/packages/client-agent-team/lib/types/client/multi-menu-field.js +30 -0
  70. package/packages/client-agent-team/lib/types/client/navigation.d.ts +16 -1
  71. package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/navigation.js +45 -8
  73. package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts +35 -0
  74. package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts.map +1 -0
  75. package/packages/client-agent-team/lib/types/client/sidebar-drag.js +70 -0
  76. package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts +27 -0
  77. package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts.map +1 -0
  78. package/packages/client-agent-team/lib/types/client/sidebar-order.js +170 -0
  79. package/packages/client-agent-team/lib/types/client/slots.d.ts +18 -2
  80. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/task-refs.d.ts +20 -0
  82. package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -0
  83. package/packages/client-agent-team/lib/types/client/task-refs.js +58 -0
  84. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +15 -0
  85. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  86. package/packages/client-agent-team/lib/types/client/team-formatters.js +30 -2
  87. package/packages/client-agent-team/lib/types/client/team-separators.d.ts +8 -0
  88. package/packages/client-agent-team/lib/types/client/team-separators.d.ts.map +1 -1
  89. package/packages/client-agent-team/lib/types/client/team-separators.js +14 -0
  90. package/packages/tool-agent-team/lib/index.js +10 -6
  91. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
@@ -2,6 +2,9 @@ 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";
6
+ import { SortableRow, useSidebarRowDrag } from "./sidebar-drag.js";
7
+ import { moveSidebarItem, useSidebarOrder } from "./sidebar-order.js";
5
8
  import { TeamRowMenu } from "./TeamRowMenu.js";
6
9
  import { TeamSidebarSection } from "./TeamSidebarSection.js";
7
10
  import { useChannelMembership } from "./team-membership.js";
@@ -21,6 +24,18 @@ export function TeamChannelsPanel(props) {
21
24
  const [pendingCreate, setPendingCreate] = useState();
22
25
  const previousCreatingKey = useRef(creatingAgents.map(request => request.requestId).join(','));
23
26
  const triggerRef = useRef(null);
27
+ // Row order is this browser's presentation preference; the drag commits
28
+ // through the single shared mutation below.
29
+ const channelRefs = useMemo(() => view?.channels.map(channel => channel.channelRef) ?? [], [view]);
30
+ const orderedChannelRefs = useSidebarOrder(workspaceId, 'channels', channelRefs);
31
+ const orderedChannels = useMemo(() => {
32
+ const byRef = new Map((view?.channels ?? []).map(channel => [channel.channelRef, channel]));
33
+ return orderedChannelRefs.map(channelRef => byRef.get(channelRef)).filter(channel => channel !== undefined);
34
+ }, [orderedChannelRefs, view]);
35
+ const applyMove = (movedRef, targetRef, marker) => {
36
+ void moveSidebarItem(workspaceId, 'channels', orderedChannelRefs, movedRef, targetRef, marker);
37
+ };
38
+ const drag = useSidebarRowDrag({ refs: orderedChannelRefs, onCommit: applyMove });
24
39
  const refresh = useCallback(async () => {
25
40
  setLoading(true);
26
41
  const [channelResult, memberResult] = await Promise.all([
@@ -76,7 +91,7 @@ export function TeamChannelsPanel(props) {
76
91
  };
77
92
  const submit = async (event) => {
78
93
  event.preventDefault();
79
- if (mutating || name.trim() === '' || description.trim() === '')
94
+ if (mutating || name.trim() === '')
80
95
  return;
81
96
  const payload = { workspaceId, name: name.trim(), description: description.trim(), memberIds: [...selected] };
82
97
  const samePending = pendingCreate !== undefined && pendingCreate.workspaceId === payload.workspaceId
@@ -110,23 +125,27 @@ export function TeamChannelsPanel(props) {
110
125
  setMutating(false);
111
126
  }
112
127
  };
113
- return (_jsxs("div", { className: css.panel, children: [_jsx(Modal, { open: formOpen, onClose: closeForm, title: t('addChannel'), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: mutating, onClick: closeForm, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-channel-create-form", variant: "primary", disabled: mutating || name.trim() === '' || description.trim() === '', children: mutating ? t('creatingChannel') : t('createChannel') })] }), children: _jsxs("form", { id: "team-channel-create-form", className: createCss.form, onSubmit: event => { void submit(event); }, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelName') }), _jsx(Input, { className: createCss.input, value: name, disabled: mutating, autoFocus: true, onChange: event => { setName(event.target.value); setPendingCreate(undefined); } })] }), _jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelDescription') }), _jsx(Input, { className: createCss.input, value: description, disabled: mutating, onChange: event => { setDescription(event.target.value); setPendingCreate(undefined); } })] }), _jsxs("fieldset", { className: createCss.memberPicker, disabled: mutating, children: [_jsx("legend", { children: t('initialMembers') }), creatingAgents.map(request => (_jsxs("label", { className: createCss.memberOption, children: [_jsx("input", { type: "checkbox", disabled: true, "aria-describedby": `creating-member-reason-${request.requestId}` }), _jsx("span", { className: createCss.unavailableDot, "aria-hidden": "true" }), _jsx("span", { children: request.handle }), _jsx("small", { id: `creating-member-reason-${request.requestId}`, children: t('memberCreatingReason') })] }, request.requestId))), members.map(status => {
114
- const disabled = status.presence === 'unavailable';
115
- const reasonId = `initial-member-reason-${status.member.memberId}`;
116
- return (_jsxs("label", { className: createCss.memberOption, children: [_jsx("input", { type: "checkbox", disabled: disabled, "aria-describedby": disabled ? reasonId : undefined, checked: selected.has(status.member.memberId), onChange: event => {
117
- setSelected(current => {
118
- const next = new Set(current);
119
- if (event.target.checked)
120
- next.add(status.member.memberId);
121
- else
122
- next.delete(status.member.memberId);
123
- return next;
124
- });
125
- setPendingCreate(undefined);
126
- } }), _jsx(TeamPresenceDot, { status: status, t: t }), _jsx("span", { children: status.member.handle }), disabled && _jsx("small", { id: reasonId, children: t('memberUnavailableReason') })] }, status.member.memberId));
127
- })] }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('channels'), actions: (_jsx(Tooltip, { label: t('addChannel'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addChannel'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && view === undefined && _jsx("p", { className: css.emptyState, children: t('loadingChannels') }), !loading && (view?.channels.length ?? 0) === 0 && _jsx("p", { className: css.emptyState, children: t('emptyChannels') }), _jsx("div", { className: css.channelList, children: view?.channels.map(channel => {
128
+ 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: [
129
+ ...creatingAgents.map(request => ({ id: request.requestId, label: request.handle, disabled: true, hint: t('memberCreatingReason') })),
130
+ ...members.map(status => ({
131
+ id: status.member.memberId,
132
+ label: status.member.handle,
133
+ ...(status.presence === 'unavailable' ? { disabled: true, hint: t('memberUnavailableReason') } : {}),
134
+ icon: _jsx(TeamPresenceDot, { status: status, t: t }),
135
+ })),
136
+ ], selected: [...selected], onToggle: id => {
137
+ setSelected(current => {
138
+ const next = new Set(current);
139
+ if (next.has(id))
140
+ next.delete(id);
141
+ else
142
+ next.add(id);
143
+ return next;
144
+ });
145
+ setPendingCreate(undefined);
146
+ }, triggerEmptyLabel: t('membersPickerEmpty'), formatCount: count => t('membersPickerCount', { count }) }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('channels'), actions: (_jsx(Tooltip, { label: t('addChannel'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addChannel'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && view === undefined && _jsx("p", { className: css.emptyState, children: t('loadingChannels') }), !loading && (view?.channels.length ?? 0) === 0 && _jsx("p", { className: css.emptyState, children: t('emptyChannels') }), _jsx("div", { className: css.channelList, children: orderedChannels.map(channel => {
128
147
  const joined = membership.get(channel.channelRef) ?? new Set();
129
- return (_jsx(ChannelRow, { channel: channel, members: members, joinedIds: joined, selected: selectedChannelRef === channel.channelRef, updateChannel: updateChannel, joinChannel: props.joinChannel, removeChannelMember: props.removeChannelMember, onCommitted: () => { void refresh(); }, selectChannel: selectChannel, t: t }, channel.channelRef));
148
+ return (_jsx(SortableRow, { drag: drag, orderKey: channel.channelRef, children: _jsx(ChannelRow, { channel: channel, members: members, joinedIds: joined, selected: selectedChannelRef === channel.channelRef, updateChannel: updateChannel, joinChannel: props.joinChannel, removeChannelMember: props.removeChannelMember, onCommitted: () => { void refresh(); }, selectChannel: selectChannel, t: t }) }, channel.channelRef));
130
149
  }) })] }), !formOpen && error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] }));
131
150
  }
132
151
  /**
@@ -154,7 +173,7 @@ function ChannelEditorDialog({ channel, members, joinedIds, updateChannel, joinC
154
173
  event.preventDefault();
155
174
  const normalizedName = name.trim();
156
175
  const normalizedDescription = description.trim();
157
- if (saving || !dirty || normalizedName === '' || normalizedDescription === '')
176
+ if (saving || !dirty || normalizedName === '')
158
177
  return;
159
178
  const samePending = pendingRequest.current !== undefined && pendingRequest.current.channelRef === channel.channelRef
160
179
  && pendingRequest.current.name === normalizedName && pendingRequest.current.description === normalizedDescription;
@@ -186,7 +205,7 @@ function ChannelEditorDialog({ channel, members, joinedIds, updateChannel, joinC
186
205
  setSaving(false);
187
206
  }
188
207
  };
189
- return (_jsx(Modal, { open: true, onClose: onClose, title: t('editChannel'), description: `# ${channel.name}`, closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: saving, onClick: onClose, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-channel-edit-form", variant: "primary", disabled: saving || !dirty || name.trim() === '' || description.trim() === '', children: saving ? t('editSaving') : t('editSave') })] }), children: _jsxs("form", { id: "team-channel-edit-form", className: createCss.form, onSubmit: event => { void submit(event); }, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelName') }), _jsx(Input, { className: createCss.input, value: name, onChange: event => { setName(event.target.value); pendingRequest.current = undefined; }, disabled: saving, autoFocus: true })] }), _jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelDescription') }), _jsx(Input, { className: createCss.input, value: description, onChange: event => { setDescription(event.target.value); pendingRequest.current = undefined; }, disabled: saving })] }), _jsxs("fieldset", { className: createCss.memberPicker, disabled: saving, children: [_jsx("legend", { children: t('channelMembersSection') }), members.map(status => {
208
+ 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
209
  const joined = joinedIds.has(status.member.memberId);
191
210
  const rowPending = membership.pending.has(status.member.memberId);
192
211
  const disabled = rowPending || (!joined && status.presence === 'unavailable');
@@ -1,6 +1,6 @@
1
1
  import type { AgentTeamClientMemberStatus, AgentTeamMemberId } from '@wowyuarm/dsh-agent-team/types';
2
2
  import type { TeamConversationProps } from './slots.ts';
3
- export declare function TeamComposer({ members, recipients, draft, pending, confirmation, error, onDraftChange, onRecipientsChange, onSubmit, t }: {
3
+ export declare function TeamComposer({ members, recipients, draft, pending, confirmation, error, onDraftChange, onRecipientsChange, onSubmit, pendingFiles, onFilesChange, t }: {
4
4
  readonly members: readonly AgentTeamClientMemberStatus[];
5
5
  readonly recipients: ReadonlySet<AgentTeamMemberId>;
6
6
  readonly draft: string;
@@ -10,6 +10,9 @@ export declare function TeamComposer({ members, recipients, draft, pending, conf
10
10
  readonly onDraftChange: (draft: string) => void;
11
11
  readonly onRecipientsChange: (recipients: ReadonlySet<AgentTeamMemberId>) => void;
12
12
  readonly onSubmit: () => void;
13
+ /** Upload-capable surfaces pass this to enable the "+" file picker; the reply path omits it. */
14
+ readonly pendingFiles?: readonly File[];
15
+ readonly onFilesChange?: (files: readonly File[]) => void;
13
16
  readonly t: TeamConversationProps['t'];
14
17
  }): import("react").JSX.Element;
15
18
  //# sourceMappingURL=TeamComposer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TeamComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamComposer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAEpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAoCvD,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE;IACzI,QAAQ,CAAC,OAAO,EAAE,SAAS,2BAA2B,EAAE,CAAA;IACxD,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAA;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/C,QAAQ,CAAC,kBAAkB,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAA;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;IAC7B,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC,+BAkKA"}
1
+ {"version":3,"file":"TeamComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamComposer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAEpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAkDvD,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE;IACtK,QAAQ,CAAC,OAAO,EAAE,SAAS,2BAA2B,EAAE,CAAA;IACxD,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAA;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/C,QAAQ,CAAC,kBAAkB,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAA;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;IAC7B,gGAAgG;IAChG,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,IAAI,EAAE,CAAA;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,KAAK,IAAI,CAAA;IACzD,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC,+BAiOA"}
@@ -1,8 +1,9 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useLayoutEffect, useRef, useState } from 'react';
3
- import { IconSendOutline16, useAnchoredMaxHeight, useDismissOnOutsidePointer } from '@deepseek-ai/dsh-client-ui-primitives';
3
+ import { IconPlusOutline16, IconSendOutline16, useAnchoredMaxHeight, useDismissOnOutsidePointer } from '@deepseek-ai/dsh-client-ui-primitives';
4
4
  import { TeamPresenceDot } from "./TeamPresenceDot.js";
5
5
  import css from './composer.module.css';
6
+ import { formatByteSize } from "./attachment-preview.js";
6
7
  function findMention(draft, caret) {
7
8
  const beforeCaret = draft.slice(0, caret);
8
9
  if (beforeCaret.length === 0 || /\s/u.test(beforeCaret.at(-1) ?? ''))
@@ -29,8 +30,21 @@ function mentionCandidates(members, query) {
29
30
  && status.member.state !== 'inactive'
30
31
  && status.member.handle.toLocaleLowerCase().startsWith(normalized));
31
32
  }
32
- export function TeamComposer({ members, recipients, draft, pending, confirmation, error, onDraftChange, onRecipientsChange, onSubmit, t }) {
33
+ /** One object URL per draft file; revoked when the draft is removed. */
34
+ const draftPreviewUrls = new WeakMap();
35
+ function draftPreviewUrl(file) {
36
+ if (!file.type.startsWith('image/'))
37
+ return undefined;
38
+ let url = draftPreviewUrls.get(file);
39
+ if (url === undefined) {
40
+ url = URL.createObjectURL(file);
41
+ draftPreviewUrls.set(file, url);
42
+ }
43
+ return url;
44
+ }
45
+ export function TeamComposer({ members, recipients, draft, pending, confirmation, error, onDraftChange, onRecipientsChange, onSubmit, pendingFiles, onFilesChange, t }) {
33
46
  const inputRef = useRef(null);
47
+ const fileInputRef = useRef(null);
34
48
  const rootRef = useRef(null);
35
49
  const menuRef = useRef(null);
36
50
  const composingRef = useRef(false);
@@ -57,6 +71,22 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
57
71
  }
58
72
  setHighlight(current => Math.min(current, candidates.length - 1));
59
73
  }, [mention, candidates.length]);
74
+ // Match the resident DSH composer without stealing a later user choice: Team
75
+ // data can load after navigation, so a dialog or another control may already
76
+ // own focus by the time this composer appears.
77
+ useEffect(() => {
78
+ const active = document.activeElement;
79
+ if (active !== document.body && active?.closest('[aria-current="page"]') === null)
80
+ return;
81
+ inputRef.current?.focus({ preventScroll: true });
82
+ }, []);
83
+ // Confirmation settles after a read-only submission span. Restore focus in
84
+ // case the browser moved it so the second Enter can confirm immediately.
85
+ useEffect(() => {
86
+ if (confirmation === undefined || pending)
87
+ return;
88
+ inputRef.current?.focus({ preventScroll: true });
89
+ }, [confirmation, pending]);
60
90
  const pruneRecipients = (nextDraft) => {
61
91
  const knownMembers = new Map(members.map(status => [status.member.memberId, status.member]));
62
92
  const next = new Set([...recipients].filter(memberId => {
@@ -66,6 +96,15 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
66
96
  if (next.size !== recipients.size)
67
97
  onRecipientsChange(next);
68
98
  };
99
+ // Restored drafts may carry recipients that no longer match the text (or
100
+ // unknown Members); converge on mount and on every state change so the
101
+ // cached entry never stays stale — the same rule user edits already apply.
102
+ // An unloaded roster must never judge recipients unknown.
103
+ useEffect(() => {
104
+ if (members.length === 0)
105
+ return;
106
+ pruneRecipients(draft);
107
+ }, [draft, recipients, members]);
69
108
  const updateMention = (nextDraft, caret) => {
70
109
  const match = findMention(nextDraft, caret);
71
110
  setMention(match);
@@ -133,6 +172,7 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
133
172
  onSubmit();
134
173
  };
135
174
  return _jsxs("form", { ref: rootRef, className: css.root, onSubmit: event => {
175
+ console.log('DBG composer submit pending=%s draft=%j', pending, draft);
136
176
  event.preventDefault();
137
177
  if (!pending && draft.trim() !== '')
138
178
  onSubmit();
@@ -140,5 +180,22 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
140
180
  const optionId = `${listId}-${status.member.memberId}`;
141
181
  const selected = index === highlight;
142
182
  return _jsxs("button", { id: optionId, type: "button", role: "option", "aria-selected": selected, className: css.mentionOption, onMouseDown: event => { event.preventDefault(); }, onClick: () => { selectMention(status); }, children: [_jsx(TeamPresenceDot, { status: status, t: t }), _jsxs("span", { className: css.mentionName, children: ["@", status.member.handle] }), _jsx("span", { className: css.mentionDescription, children: status.member.description })] }, status.member.memberId);
143
- }) }), _jsx("textarea", { ref: inputRef, "aria-label": t('messageDraft'), "aria-autocomplete": "list", "aria-controls": menuOpen ? listId : undefined, "aria-activedescendant": menuOpen && activeCandidate !== undefined ? `${listId}-${activeCandidate.member.memberId}` : undefined, "aria-expanded": menuOpen, value: draft, disabled: pending, placeholder: t('messagePlaceholder'), rows: 1, onChange: onChange, onKeyDown: onKeyDown, onSelect: event => { updateMention(event.currentTarget.value, event.currentTarget.selectionStart ?? event.currentTarget.value.length); }, onCompositionStart: () => { composingRef.current = true; }, onCompositionEnd: () => { setTimeout(() => { composingRef.current = false; }, 10); } })] }), recipients.size > 0 && _jsx("p", { className: css.notifyRow, "data-team-notify": true, children: t('composerNotify', { ids: [...recipients].sort().map(memberId => `@${members.find(candidate => candidate.member.memberId === memberId)?.member.handle ?? memberId}`).join(', ') }) }), _jsx("div", { className: css.toolbar, children: _jsx("button", { type: "submit", className: css.sendButton, "aria-label": pending ? t('sendingMessage') : t('sendMessage'), disabled: pending || draft.trim() === '', children: _jsx(IconSendOutline16, { size: 16 }) }) })] }), error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] });
183
+ }) }), _jsx("textarea", { ref: inputRef, "aria-label": t('messageDraft'), "aria-autocomplete": "list", "aria-controls": menuOpen ? listId : undefined, "aria-activedescendant": menuOpen && activeCandidate !== undefined ? `${listId}-${activeCandidate.member.memberId}` : undefined, "aria-expanded": menuOpen, value: draft, readOnly: pending, placeholder: t('messagePlaceholder'), rows: 1, onChange: onChange, onKeyDown: onKeyDown, onSelect: event => { updateMention(event.currentTarget.value, event.currentTarget.selectionStart ?? event.currentTarget.value.length); }, onCompositionStart: () => { composingRef.current = true; }, onCompositionEnd: () => { setTimeout(() => { composingRef.current = false; }, 10); } })] }), recipients.size > 0 && _jsx("p", { className: css.notifyRow, "data-team-notify": true, children: t('composerNotify', { ids: [...recipients].sort().map(memberId => `@${members.find(candidate => candidate.member.memberId === memberId)?.member.handle ?? memberId}`).join(', ') }) }), onFilesChange !== undefined && pendingFiles !== undefined && pendingFiles.length > 0 && (_jsx("ul", { className: css.fileChips, "aria-label": t('attachFiles'), children: pendingFiles.map((file, index) => {
184
+ const previewUrl = draftPreviewUrl(file);
185
+ return _jsxs("li", { className: `${css.fileChip} ${previewUrl !== undefined ? css.imageChip : ''}`, children: [previewUrl !== undefined && _jsx("img", { src: previewUrl, alt: "", className: css.imageChipPreview }), _jsxs("span", { className: css.fileChipName, title: file.name, children: [file.name, _jsx("span", { className: css.fileChipSize, children: formatByteSize(file.size) })] }), _jsx("button", { type: "button", className: css.fileChipRemove, "aria-label": t('removeFile', { name: file.name }), disabled: pending, onClick: () => {
186
+ const url = draftPreviewUrls.get(file);
187
+ if (url !== undefined)
188
+ URL.revokeObjectURL(url);
189
+ onFilesChange(pendingFiles.filter((_, candidate) => candidate !== index));
190
+ }, children: "\u00D7" })] }, `${file.name}-${index}`);
191
+ }) })), _jsxs("div", { className: css.toolbar, children: [onFilesChange !== undefined && (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInputRef, type: "file", multiple: true, className: css.fileInput, "aria-hidden": "true", tabIndex: -1, onChange: event => {
192
+ const chosen = [...event.target.files ?? []];
193
+ if (chosen.length > 0 && pendingFiles !== undefined)
194
+ onFilesChange([...pendingFiles, ...chosen]);
195
+ event.target.value = '';
196
+ } }), _jsx("button", { type: "button", className: css.attachButton, "aria-label": t('attachFiles'), title: t('attachFiles'), disabled: pending, onClick: () => { fileInputRef.current?.click(); }, children: _jsx(IconPlusOutline16, { size: 14 }) })] })), _jsx("button", { type: "submit", className: css.sendButton, "aria-label": pending ? t('sendingMessage') : t('sendMessage'), disabled: pending || draft.trim() === '', onMouseDown: event => {
197
+ // Keep the composer focused when the send control is clicked.
198
+ event.preventDefault();
199
+ inputRef.current?.focus({ preventScroll: true });
200
+ }, children: _jsx(IconSendOutline16, { size: 16 }) })] })] }), error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] });
144
201
  }
@@ -1,3 +1,3 @@
1
1
  import type { TeamConversationProps } from './slots.ts';
2
- export declare function TeamConversation({ t, useWorkspaces, navigation, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, reply, changeTask, selectThread, backToWorkspace, backToChannels }: TeamConversationProps): import("react").JSX.Element;
2
+ export declare function TeamConversation({ t, useWorkspaces, navigation, drafts, putAttachment, getAttachment, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, reply, changeTask, selectThread, selectChannel, backToWorkspace, backToChannels, resolveTaskRefs }: TeamConversationProps): import("react").JSX.Element;
3
3
  //# sourceMappingURL=TeamConversation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TeamConversation.d.ts","sourceRoot":"","sources":["../../../src/client/TeamConversation.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAKvD,wBAAgB,gBAAgB,CAAC,EAAE,CAAC,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,EAAE,qBAAqB,+BAoBpR"}
1
+ {"version":3,"file":"TeamConversation.d.ts","sourceRoot":"","sources":["../../../src/client/TeamConversation.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAKvD,wBAAgB,gBAAgB,CAAC,EAAE,CAAC,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,EAAE,qBAAqB,+BAoB1V"}
@@ -3,15 +3,15 @@ import { useSyncExternalStore } from 'react';
3
3
  import { TeamChannelPage } from "./TeamChannelPage.js";
4
4
  import { TeamThreadPage } from "./TeamThreadPage.js";
5
5
  import css from './conversation.module.css';
6
- export function TeamConversation({ t, useWorkspaces, navigation, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, reply, changeTask, selectThread, backToWorkspace, backToChannels }) {
6
+ export function TeamConversation({ t, useWorkspaces, navigation, drafts, putAttachment, getAttachment, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, reply, changeTask, selectThread, selectChannel, backToWorkspace, backToChannels, resolveTaskRefs }) {
7
7
  const navigationState = useSyncExternalStore(navigation.subscribe, navigation.getSnapshot, navigation.getSnapshot);
8
8
  const workspaces = useWorkspaces(state => state.items);
9
9
  const current = workspaces.find(workspace => workspace.workspaceId === navigationState.workspaceId);
10
10
  if (current !== undefined && navigationState.taskRef !== undefined && navigationState.threadRef !== undefined) {
11
- return _jsx(TeamThreadPage, { workspaceId: current.workspaceId, taskRef: navigationState.taskRef, threadRef: navigationState.threadRef, backToWorkspace: backToWorkspace, ...(navigationState.channelRef === undefined ? {} : { channelRef: navigationState.channelRef }), ...(navigationState.taskNumber === undefined ? {} : { taskNumber: navigationState.taskNumber }), readThread: readThread, loadChannels: loadChannels, loadThreadHistory: loadThreadHistory, subscribeChanges: subscribeChanges, loadMembers: loadMembers, reply: reply, changeTask: changeTask, t: t }, navigationState.threadRef);
11
+ return _jsx(TeamThreadPage, { workspaceId: current.workspaceId, putAttachment: putAttachment, taskRef: navigationState.taskRef, threadRef: navigationState.threadRef, backToWorkspace: backToWorkspace, selectChannel: selectChannel, selectThread: selectThread, resolveTaskRefs: resolveTaskRefs, ...(navigationState.channelRef === undefined ? {} : { channelRef: navigationState.channelRef }), ...(navigationState.taskNumber === undefined ? {} : { taskNumber: navigationState.taskNumber }), drafts: drafts, getAttachment: getAttachment, readThread: readThread, loadChannels: loadChannels, loadThreadHistory: loadThreadHistory, subscribeChanges: subscribeChanges, loadMembers: loadMembers, reply: reply, changeTask: changeTask, t: t }, navigationState.threadRef);
12
12
  }
13
13
  if (current !== undefined && navigationState.channelRef !== undefined) {
14
- return _jsx(TeamChannelPage, { workspaceId: current.workspaceId, channelRef: navigationState.channelRef, loadChannels: loadChannels, subscribeChanges: subscribeChanges, loadMembers: loadMembers, sendMessage: sendMessage, joinChannel: joinChannel, removeChannelMember: removeChannelMember, selectThread: selectThread, backToChannels: backToChannels, t: t }, navigationState.channelRef);
14
+ return _jsx(TeamChannelPage, { workspaceId: current.workspaceId, channelRef: navigationState.channelRef, drafts: drafts, putAttachment: putAttachment, getAttachment: getAttachment, loadChannels: loadChannels, subscribeChanges: subscribeChanges, loadMembers: loadMembers, sendMessage: sendMessage, joinChannel: joinChannel, removeChannelMember: removeChannelMember, selectThread: selectThread, selectChannel: selectChannel, backToChannels: backToChannels, resolveTaskRefs: resolveTaskRefs, t: t }, navigationState.channelRef);
15
15
  }
16
16
  const welcome = current === undefined
17
17
  ? { eyebrow: t('teamMode'), title: t('team'), body: t('empty') }
@@ -1,5 +1,7 @@
1
1
  import { type ReactNode } from 'react';
2
- import type { AgentTeamMemberId } from '@wowyuarm/dsh-agent-team/types';
2
+ import type { AgentTeamMemberId, AgentTeamMessageAttachment, AgentTeamTaskRef } from '@wowyuarm/dsh-agent-team/types';
3
+ import type { TeamConversationProps } from './slots.ts';
4
+ import { type ResolvedTaskRef } from './task-refs.ts';
3
5
  export interface TeamMessageProps {
4
6
  readonly senderName: string;
5
7
  readonly memberId: AgentTeamMemberId;
@@ -12,8 +14,16 @@ export interface TeamMessageProps {
12
14
  readonly senderTitle?: string;
13
15
  /** Continuation of one same-sender run: suppress repeated identity chrome. */
14
16
  readonly grouped?: boolean;
17
+ readonly attachments?: readonly AgentTeamMessageAttachment[] | undefined;
18
+ /** Cache readback for thumbnails; absent on surfaces without the remotes. */
19
+ readonly loadAttachment?: TeamConversationProps['getAttachment'] | undefined;
20
+ readonly t?: TeamConversationProps['t'] | undefined;
21
+ /** Resolve a branded ref found in the body; absent surfaces render refs as plain text. */
22
+ readonly onOpenRef?: ((ref: string) => void) | undefined;
23
+ /** Host lookup turning task refs into human-facing numbers; absent keeps raw refs. */
24
+ readonly onResolveTaskRefs?: ((taskRefs: readonly AgentTeamTaskRef[]) => Promise<readonly ResolvedTaskRef[]>) | undefined;
15
25
  readonly children?: ReactNode;
16
26
  }
17
27
  /** One chat message row with identity chrome and sender-appropriate rendering. */
18
- export declare function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, children }: TeamMessageProps): import("react").JSX.Element;
28
+ export declare function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, attachments, loadAttachment, t, onOpenRef, onResolveTaskRefs, children }: TeamMessageProps): import("react").JSX.Element;
19
29
  //# sourceMappingURL=TeamMessage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TeamMessage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAIvE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,uFAAuF;IACvF,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAC9B;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BAsC5I"}
1
+ {"version":3,"file":"TeamMessage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2F,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAE/H,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AACrH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,EAA4E,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAI/H,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,uFAAuF;IACvF,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,0BAA0B,EAAE,GAAG,SAAS,CAAA;IACxE,6EAA6E;IAC7E,QAAQ,CAAC,cAAc,CAAC,EAAE,qBAAqB,CAAC,eAAe,CAAC,GAAG,SAAS,CAAA;IAC5E,QAAQ,CAAC,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;IACnD,0FAA0F;IAC1F,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACxD,sFAAsF;IACtF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,KAAK,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IACzH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAC9B;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BAkG1M"}
@@ -1,24 +1,162 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Fragment } from 'react';
3
- import { MarkdownText, MessageText } from '@deepseek-ai/dsh-client-ui-primitives';
4
- import { formatMessageTime, isPlainTextBody, memberHue, splitMentionNames } from "./team-formatters.js";
2
+ import { Fragment, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
3
+ import { MarkdownText, MessageText, Modal } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import { cachedAttachmentDataUrl, formatByteSize, loadAttachmentDataUrl } from "./attachment-preview.js";
5
+ import { cachedResolvedTaskRef, resolveUnknownTaskRefs, useResolvedTaskRefVersion } from "./task-refs.js";
6
+ import { formatMessageTime, isPlainTextBody, memberHue, splitBrandedRefs, splitMentionNames, stripAttachmentLines } from "./team-formatters.js";
5
7
  import css from './conversation.module.css';
6
8
  /** One chat message row with identity chrome and sender-appropriate rendering. */
7
- export function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, children }) {
9
+ export function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, attachments, loadAttachment, t, onOpenRef, onResolveTaskRefs, children }) {
8
10
  const avatarStyle = human ? undefined : { '--team-avatar-hue': memberHue(memberId) };
9
- // Literal bodies carry the chips inline — Human input always, and plain-
10
- // prose Agent bodies where literal rendering loses nothing. Rich Markdown
11
- // Agent bodies fall back to the trailing chip row: the Markdown primitive
12
- // renders block-level documents, so chips cannot interleave mid-paragraph.
13
- const inline = (human || isPlainTextBody(body)) && mentionNames !== undefined && mentionNames.length > 0
14
- ? splitMentionNames(body, mentionNames)
11
+ // Literal bodies carry mention chips inline — Human input always, and
12
+ // plain-prose Agent bodies where literal rendering loses nothing. Rich
13
+ // Markdown keeps unmatched structured mentions in the trailing row.
14
+ // The stored body carries machine-facing `[attachment] <path>` prompt lines;
15
+ // humans see the attachment strip rendered from the message metadata instead.
16
+ const displayBody = stripAttachmentLines(body) === '' ? body : stripAttachmentLines(body);
17
+ const inline = (human || isPlainTextBody(displayBody)) && mentionNames !== undefined && mentionNames.length > 0
18
+ ? splitMentionNames(displayBody, mentionNames)
15
19
  : undefined;
16
20
  const fallbackNames = inline === undefined ? (mentionNames ?? []) : inline.unmatched;
21
+ const richAgentBody = !human && !isPlainTextBody(displayBody);
22
+ // Non-Task branded refs retain the legacy fallback row. Task refs instead
23
+ // stay at their authored prose position; unresolved refs remain literal.
24
+ const fallbackRefs = richAgentBody && onOpenRef !== undefined
25
+ ? splitBrandedRefs(displayBody).filter(segment => segment.ref !== undefined && !segment.ref.startsWith('task:')).map(segment => segment.ref)
26
+ : [];
27
+ // Resolved refs re-label once the Host lookup lands; the version token
28
+ // refreshes literal links and rendered Markdown prose.
29
+ const refVersion = useResolvedTaskRefVersion();
30
+ const bodyTaskRefs = onOpenRef === undefined || richAgentBody ? [] : splitBrandedRefs(displayBody)
31
+ .filter(segment => segment.ref !== undefined && segment.ref.startsWith('task:'))
32
+ .map(segment => segment.ref);
33
+ const bodyTaskRefKey = bodyTaskRefs.join(',');
34
+ useEffect(() => {
35
+ if (onResolveTaskRefs === undefined || bodyTaskRefKey === '')
36
+ return;
37
+ void resolveUnknownTaskRefs(bodyTaskRefs, onResolveTaskRefs);
38
+ }, [onResolveTaskRefs, bodyTaskRefKey, refVersion]);
39
+ const taskLabel = useCallback((taskNumber) => t?.('taskLabel', { number: taskNumber }) ?? `Task #${taskNumber}`, [t]);
40
+ const markdownRef = useRef(null);
41
+ useLayoutEffect(() => {
42
+ const root = markdownRef.current;
43
+ if (!richAgentBody || root === null || onOpenRef === undefined)
44
+ return;
45
+ const textNodes = markdownProseTextNodes(root);
46
+ const taskRefs = [...new Set(textNodes.flatMap(node => splitBrandedRefs(node.data)
47
+ .filter(segment => segment.ref?.startsWith('task:') === true)
48
+ .map(segment => segment.ref)))];
49
+ if (onResolveTaskRefs !== undefined && taskRefs.length > 0)
50
+ void resolveUnknownTaskRefs(taskRefs, onResolveTaskRefs);
51
+ for (const node of textNodes)
52
+ renderResolvedMarkdownTaskRefs(node, taskLabel);
53
+ for (const button of root.querySelectorAll('button[data-task-ref]')) {
54
+ const taskRef = button.dataset.taskRef;
55
+ const hit = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
56
+ if (taskRef !== undefined && hit !== undefined)
57
+ button.textContent = taskLabel(hit.taskNumber);
58
+ }
59
+ }, [richAgentBody, onOpenRef, onResolveTaskRefs, refVersion, taskLabel]);
60
+ useEffect(() => {
61
+ const root = markdownRef.current;
62
+ if (!richAgentBody || root === null || onOpenRef === undefined)
63
+ return;
64
+ const openTask = (event) => {
65
+ const target = event.target;
66
+ if (!(target instanceof Element))
67
+ return;
68
+ const button = target.closest('button[data-task-ref]');
69
+ if (button === null || !root.contains(button))
70
+ return;
71
+ const taskRef = button instanceof HTMLElement ? button.dataset.taskRef : undefined;
72
+ if (taskRef !== undefined)
73
+ onOpenRef(taskRef);
74
+ };
75
+ root.addEventListener('click', openTask);
76
+ return () => { root.removeEventListener('click', openTask); };
77
+ }, [richAgentBody, onOpenRef]);
17
78
  return (_jsxs("article", { className: css.messageRow, "data-human": human || undefined, "data-grouped": grouped || undefined, children: [_jsx("div", { className: css.messageIdentity, style: avatarStyle, "aria-hidden": "true", children: senderName.replace('@', '').slice(0, 1).toUpperCase() }), _jsxs("div", { className: css.messageBody, children: [!grouped && (_jsxs("div", { className: css.nameRow, children: [_jsx("strong", { ...(senderTitle === undefined ? {} : { title: senderTitle }), children: senderName }), occurredAt !== undefined && _jsx("span", { className: css.messageTime, children: formatMessageTime(occurredAt) })] })), inline !== undefined
18
79
  ? _jsx("div", { className: css.messageText, children: inline.segments.map((segment, index) => segment.mention
19
80
  ? _jsx("span", { className: css.mention, children: segment.text }, index)
20
- : _jsx(Fragment, { children: segment.text }, index)) })
21
- : human
22
- ? _jsx("div", { className: css.messageText, children: _jsx(MessageText, { text: body }) })
23
- : _jsx("div", { className: css.messageMarkdown, children: _jsx(MarkdownText, { text: body }) }), fallbackNames.length > 0 && (_jsx("div", { className: css.mentionsRow, children: fallbackNames.map(name => _jsxs("span", { className: css.mention, children: ["@", name] }, name)) })), children] })] }));
81
+ : _jsx(Fragment, { children: renderRefs(segment.text, onOpenRef, taskLabel) }, index)) })
82
+ : human || (onOpenRef !== undefined && isPlainTextBody(displayBody) && splitBrandedRefs(displayBody).some(segment => segment.ref !== undefined))
83
+ ? _jsx("div", { className: css.messageText, children: onOpenRef === undefined ? _jsx(MessageText, { text: displayBody }) : renderRefs(displayBody, onOpenRef, taskLabel) })
84
+ : _jsx("div", { ref: markdownRef, className: css.messageMarkdown, children: _jsx(MarkdownText, { text: displayBody }, `${displayBody}:${onOpenRef === undefined ? 'literal' : 'refs'}`) }), (fallbackNames.length > 0 || fallbackRefs.length > 0) && (_jsxs("div", { className: css.mentionsRow, children: [fallbackNames.map(name => _jsxs("span", { className: css.mention, children: ["@", name] }, name)), fallbackRefs.map(ref => {
85
+ const resolved = cachedResolvedTaskRef(ref);
86
+ const label = resolved !== undefined && ref.startsWith('task:') ? taskLabel(resolved.taskNumber) : ref;
87
+ return _jsx("button", { type: "button", className: css.refLink, title: ref, onClick: () => { onOpenRef(ref); }, children: label }, ref);
88
+ })] })), attachments !== undefined && attachments.length > 0 && _jsx(TeamAttachmentStrip, { attachments: attachments, ...(loadAttachment === undefined ? {} : { loadAttachment }), ...(t === undefined ? {} : { t }) }), children] })] }));
89
+ }
90
+ /** Text nodes that Markdown rendered as prose rather than code or a link. */
91
+ function markdownProseTextNodes(root) {
92
+ const nodes = [];
93
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
94
+ for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
95
+ const parent = node.parentElement;
96
+ if (parent !== null && parent.closest('code, pre, a, button') === null)
97
+ nodes.push(node);
98
+ }
99
+ return nodes;
100
+ }
101
+ /** Replace resolved refs in one prose text node without changing Markdown structure. */
102
+ function renderResolvedMarkdownTaskRefs(node, taskLabel) {
103
+ const segments = splitBrandedRefs(node.data);
104
+ if (!segments.some(segment => segment.ref?.startsWith('task:') === true && cachedResolvedTaskRef(segment.ref) !== undefined))
105
+ return;
106
+ const fragment = document.createDocumentFragment();
107
+ for (const segment of segments) {
108
+ const taskRef = segment.ref?.startsWith('task:') === true ? segment.ref : undefined;
109
+ if (taskRef === undefined) {
110
+ fragment.append(segment.text);
111
+ continue;
112
+ }
113
+ const resolved = cachedResolvedTaskRef(taskRef);
114
+ if (resolved === undefined) {
115
+ fragment.append(segment.text);
116
+ continue;
117
+ }
118
+ const button = document.createElement('button');
119
+ button.type = 'button';
120
+ if (css.refLink !== undefined)
121
+ button.className = css.refLink;
122
+ button.dataset.taskRef = taskRef;
123
+ button.title = taskRef;
124
+ button.textContent = taskLabel(resolved.taskNumber);
125
+ fragment.append(button);
126
+ }
127
+ node.replaceWith(fragment);
128
+ }
129
+ /** Render one literal text run, linkifying branded refs when navigation is available. */
130
+ function renderRefs(text, onOpenRef, taskLabel) {
131
+ if (onOpenRef === undefined)
132
+ return text;
133
+ return splitBrandedRefs(text).map((segment, index) => {
134
+ if (segment.ref === undefined)
135
+ return _jsx(Fragment, { children: segment.text }, index);
136
+ const resolved = cachedResolvedTaskRef(segment.ref);
137
+ const label = resolved !== undefined && segment.ref.startsWith('task:') ? taskLabel(resolved.taskNumber) : segment.ref;
138
+ return _jsx("button", { type: "button", className: css.refLink, title: segment.ref, onClick: () => { onOpenRef(segment.ref); }, children: label }, index);
139
+ });
140
+ }
141
+ /** One message's attachment strip: image thumbnails with a large view, or name chips when bytes are gone. */
142
+ function TeamAttachmentStrip({ attachments, loadAttachment, t }) {
143
+ const [zoomed, setZoomed] = useState();
144
+ return _jsxs("div", { className: css.attachmentStrip, children: [attachments.map(attachment => _jsx(TeamAttachment, { attachment: attachment, ...(loadAttachment === undefined ? {} : { loadAttachment }), ...(t === undefined ? {} : { t }), onZoom: setZoomed }, attachment.attachmentId)), zoomed !== undefined && _jsx(Modal, { open: true, ...(css.attachmentModal === undefined ? {} : { className: css.attachmentModal }), title: zoomed.name, onClose: () => { setZoomed(undefined); }, children: _jsx("img", { className: css.attachmentZoom, src: cachedAttachmentDataUrl(zoomed.attachmentId) ?? undefined, alt: zoomed.name }) })] });
145
+ }
146
+ function TeamAttachment({ attachment, loadAttachment, t, onZoom }) {
147
+ const wantsPreview = loadAttachment !== undefined && attachment.mediaType.startsWith('image/');
148
+ const [dataUrl, setDataUrl] = useState(wantsPreview ? cachedAttachmentDataUrl(attachment.attachmentId) : null);
149
+ useEffect(() => {
150
+ if (!wantsPreview || dataUrl !== undefined)
151
+ return;
152
+ let mounted = true;
153
+ void loadAttachmentDataUrl(loadAttachment, attachment).then(url => { if (mounted)
154
+ setDataUrl(url); });
155
+ return () => { mounted = false; };
156
+ }, [wantsPreview, dataUrl, loadAttachment, attachment]);
157
+ const expired = t?.('attachmentExpired') ?? 'File no longer cached';
158
+ if (wantsPreview && dataUrl !== null) {
159
+ return _jsx("button", { type: "button", className: css.attachmentThumb, "aria-label": t?.('viewImage', { name: attachment.name }) ?? attachment.name, title: attachment.name, onClick: () => { onZoom(attachment); }, children: _jsx("img", { src: dataUrl, alt: attachment.name }) });
160
+ }
161
+ return _jsxs("span", { className: css.attachmentChip, title: wantsPreview ? expired : `${attachment.name} · ${formatByteSize(attachment.byteSize)}`, children: [_jsx("span", { className: css.attachmentChipName, children: attachment.name }), _jsx("span", { className: css.attachmentChipSize, children: wantsPreview ? expired : formatByteSize(attachment.byteSize) })] });
24
162
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Explicit boundary between two same-sender Messages of one run separated by
3
+ * a real waiting gap: the hairline restores the block boundary that grouping
4
+ * removed, and the label below it restores the instant that the suppressed
5
+ * identity chrome would have shown.
6
+ */
7
+ export declare function TeamRunDivider({ occurredAt }: {
8
+ readonly occurredAt: string;
9
+ }): import("react").JSX.Element;
10
+ //# sourceMappingURL=TeamRunDivider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TeamRunDivider.d.ts","sourceRoot":"","sources":["../../../src/client/TeamRunDivider.tsx"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,+BAM7E"}
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { formatMessageTime } from "./team-formatters.js";
3
+ import css from './conversation.module.css';
4
+ /**
5
+ * Explicit boundary between two same-sender Messages of one run separated by
6
+ * a real waiting gap: the hairline restores the block boundary that grouping
7
+ * removed, and the label below it restores the instant that the suppressed
8
+ * identity chrome would have shown.
9
+ */
10
+ export function TeamRunDivider({ occurredAt }) {
11
+ return (_jsx("div", { className: css.runDivider, role: "separator", children: _jsx("time", { dateTime: occurredAt, children: formatMessageTime(occurredAt) }) }));
12
+ }
@@ -1,6 +1,7 @@
1
1
  import type { AgentTeamChannelRef, AgentTeamTaskRef, AgentTeamThreadRef } 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 TeamThreadPageProps {
5
6
  readonly workspaceId: WorkspaceId;
6
7
  readonly channelRef?: AgentTeamChannelRef;
@@ -13,8 +14,14 @@ interface TeamThreadPageProps {
13
14
  readonly loadThreadHistory: TeamConversationProps['loadThreadHistory'];
14
15
  readonly subscribeChanges: TeamConversationProps['subscribeChanges'];
15
16
  readonly loadMembers: TeamConversationProps['loadMembers'];
17
+ readonly drafts: TeamDraftStore;
18
+ readonly getAttachment: TeamConversationProps['getAttachment'];
16
19
  readonly reply: TeamConversationProps['reply'];
17
20
  readonly changeTask: TeamConversationProps['changeTask'];
21
+ readonly putAttachment: TeamConversationProps['putAttachment'];
22
+ readonly selectChannel: TeamConversationProps['selectChannel'];
23
+ readonly selectThread: TeamConversationProps['selectThread'];
24
+ readonly resolveTaskRefs: TeamConversationProps['resolveTaskRefs'];
18
25
  readonly t: TeamConversationProps['t'];
19
26
  }
20
27
  export declare function TeamThreadPage(props: TeamThreadPageProps): import("react").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"TeamThreadPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamThreadPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,mBAAmB,EAInB,gBAAgB,EAGhB,kBAAkB,EAEnB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAUvD,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAA;IACzC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAA;IAClC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACxD,QAAQ,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAA;IACtE,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAA;IACpE,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAA;IAC9C,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACxD,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AA8BD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+BA8dxD"}
1
+ {"version":3,"file":"TeamThreadPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamThreadPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,mBAAmB,EAInB,gBAAgB,EAGhB,kBAAkB,EAEnB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AACvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAa/D,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAA;IACzC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAA;IAClC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACxD,QAAQ,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAA;IACtE,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,KAAK,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAA;IAC9C,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACxD,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AA8BD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+BAgkBxD"}