@yancyyu/openhermit 1.6.27 → 1.6.29
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 +7 -1
- package/bin/hermit.mjs +2 -2
- package/dist-renderer/assets/ProjectEditorOverlay-CQm6jUR1.js +52 -0
- package/dist-renderer/assets/{TeamGraphOverlay-DVq8rt6_.js → TeamGraphOverlay-h0WDfifv.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-ZbF0pKvS.js → _basePickBy-CgG_tjgX.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-BBLBOeXc.js → _baseUniq-DwPTU9lP.js} +1 -1
- package/dist-renderer/assets/{arc-wGaEgkCf.js → arc-7nIrGRzY.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BpMkdC35.js → architectureDiagram-VXUJARFQ-BYhA6Ev2.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-C8Z1xhG4.js → blockDiagram-VD42YOAC-BVpZUGDg.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-CJmlw9LA.js → c4Diagram-YG6GDRKO-DsdreMQ9.js} +1 -1
- package/dist-renderer/assets/channel-C0SqeFU7.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-CHPHiRPP.js → chunk-4BX2VUAB-CcoAs7Jd.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-DyVohOQb.js → chunk-55IACEB6-CGGAOoXd.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-p5bffh_R.js → chunk-B4BG7PRW-FhpTEPvD.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-BnfGPSUu.js → chunk-DI55MBZ5-DoYySbm1.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-B6SCKseX.js → chunk-FMBD7UC4-e9l2tGHG.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-L12RvLBR.js → chunk-QN33PNHL-DeiXVTCy.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-DeH1Kxge.js → chunk-QZHKN3VN-DC2UJLJM.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-BWnjzSlI.js → chunk-TZMSLE5B-BHFD9eZI.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +1 -0
- package/dist-renderer/assets/clone-Dm-k63Yr.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-BtzoT5fu.js → cose-bilkent-S5V4N54A-BdybQraU.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-CBBvuoUD.js → dagre-6UL2VRFP-DdF3pwM3.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-Be9BAKws.js → diagram-PSM6KHXK-B9Ldd3nh.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-BDS4PI_i.js → diagram-QEK2KX5R-XEqkrbpu.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-2Rameaq7.js → diagram-S2PKOQOG-CipwtY59.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-CSIzCEZD.js → erDiagram-Q2GNP2WA-BB-2ISGo.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-ForEIVM5.js → flowDiagram-NV44I4VS-B8XmJ0u2.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-BJrli_xr.js → ganttDiagram-JELNMOA3-D-8XglBb.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-C_4GuLno.js → gitGraphDiagram-V2S2FVAM-DL4ChakD.js} +1 -1
- package/dist-renderer/assets/{graph-B1EAT_gw.js → graph-BiFNoBjP.js} +1 -1
- package/dist-renderer/assets/{index-eKRmS5kI.js → index-6m1ZAymG.js} +1 -1
- package/dist-renderer/assets/index-BhellmRb.css +1 -0
- package/dist-renderer/assets/{index-DYdseEwc.js → index-BowUl0Jb.js} +518 -514
- package/dist-renderer/assets/{index-DR602dwJ.js → index-Dp3kJTEe.js} +1 -1
- package/dist-renderer/assets/{index-Dwr5wu5x.js → index-TOpt_T7A.js} +1 -1
- package/dist-renderer/assets/{index-DOA_jbYb.js → index-qNBNjW4K.js} +1 -1
- package/dist-renderer/assets/{index-k4tnOFC5.js → index-vAykq1H1.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DjI0uaMz.js → infoDiagram-HS3SLOUP-DRIBfHDi.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-jQ6Thae-.js → journeyDiagram-XKPGCS4Q-BOMiigU4.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CKw6InbL.js → kanban-definition-3W4ZIXB7-DDxeyjod.js} +1 -1
- package/dist-renderer/assets/{layout-Dad20y3V.js → layout-DNANbrI4.js} +1 -1
- package/dist-renderer/assets/{linear-vMgo_2Cv.js → linear-DxEJi1yT.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-DYp6YoHL.js → mindmap-definition-VGOIOE7T-nBfGriW8.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BytBecG9.js → pieDiagram-ADFJNKIX-Din5j6sV.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-RUaspLsc.js → quadrantDiagram-AYHSOK5B-DMVK2BEQ.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-rR2B1Use.js → requirementDiagram-UZGBJVZJ-6SC94Gg_.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-BJi5qYhq.js → sankeyDiagram-TZEHDZUN-CD2gghhu.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BM-wggUb.js → sequenceDiagram-WL72ISMW-BnhkN7nZ.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BqmcVjnj.js → stateDiagram-FKZM4ZOC-Bn8XdYX-.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-By3JDVbB.js → stateDiagram-v2-4FDKWEC3-1b6sI1_g.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-szH0GUyk.js → timeline-definition-IT6M3QCI-CNs3RPoa.js} +1 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +162 -0
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-dwDpvw0w.js → xychartDiagram-PRI3JC2R-B8o5J2f3.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/main/server.ts +800 -163
- package/src/main/services/session-intelligence/SessionUsageParser.ts +446 -0
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +252 -0
- package/src/main/services/teams-mvp/CollaborationBoardService.ts +310 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +880 -95
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +58 -19
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +25 -2
- package/src/main/services/teams-mvp/index.ts +3 -0
- package/src/renderer/App.tsx +5 -0
- package/src/renderer/api/httpClient.ts +67 -0
- package/src/renderer/components/dashboard/DashboardView.tsx +6 -105
- package/src/renderer/components/layout/PaneContent.tsx +2 -0
- package/src/renderer/components/layout/SortableTab.tsx +1 -0
- package/src/renderer/components/layout/TabBarActions.tsx +12 -12
- package/src/renderer/components/schedules/SchedulesView.tsx +54 -22
- package/src/renderer/components/settings/SettingsTabs.tsx +2 -2
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +1 -1
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +511 -81
- package/src/renderer/components/tasks/TasksView.tsx +343 -0
- package/src/renderer/components/team/TeamDetailView.tsx +20 -98
- package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +1 -1
- package/src/renderer/components/team/editor/EditorContextMenu.tsx +8 -23
- package/src/renderer/components/team/editor/EditorFileTree.tsx +0 -4
- package/src/renderer/components/team/editor/EditorSelectionMenu.tsx +1 -8
- package/src/renderer/components/team/editor/ProjectEditorOverlay.tsx +0 -10
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +5 -1
- package/src/renderer/components/team/members/MemberDetailDialog.tsx +8 -33
- package/src/renderer/components/team/messages/MessageComposer.tsx +39 -3
- package/src/renderer/components/team/messages/MessagesPanel.tsx +72 -2
- package/src/renderer/components/team/messages/StatusBlock.tsx +2 -24
- package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +1 -1
- package/src/renderer/components/ui/MentionableTextarea.tsx +0 -1
- package/src/renderer/store/slices/scheduleSlice.ts +21 -0
- package/src/renderer/store/slices/teamSlice.ts +59 -23
- package/src/renderer/types/tabs.ts +1 -0
- package/src/shared/types/api.ts +29 -0
- package/src/shared/types/team.ts +109 -1
- package/dist-renderer/assets/ProjectEditorOverlay-BBwYdXPv.js +0 -57
- package/dist-renderer/assets/channel-DJUrwVrK.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-blc3DrH7.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-blc3DrH7.js +0 -1
- package/dist-renderer/assets/clone-BftjWakJ.js +0 -1
- package/dist-renderer/assets/index-CWpFqEvz.css +0 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-BCMlh-Ex.js +0 -162
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Button } from '@renderer/components/ui/button';
|
|
4
|
+
import { cn } from '@renderer/lib/utils';
|
|
5
|
+
import { useStore } from '@renderer/store';
|
|
6
|
+
import { deriveTaskDisplayId } from '@shared/utils/taskIdentity';
|
|
7
|
+
import { Calendar, CheckCircle2, Circle, Columns3, Loader2, RefreshCw } from 'lucide-react';
|
|
8
|
+
import { useShallow } from 'zustand/react/shallow';
|
|
9
|
+
|
|
10
|
+
import { SchedulesView } from '../schedules/SchedulesView';
|
|
11
|
+
|
|
12
|
+
import type { GlobalTask, TeamTaskStatus } from '@shared/types';
|
|
13
|
+
|
|
14
|
+
type TasksSubTab = 'overview' | 'schedules';
|
|
15
|
+
type OverviewStatus = Extract<TeamTaskStatus, 'pending' | 'in_progress' | 'completed'>;
|
|
16
|
+
|
|
17
|
+
const SUB_TABS: { id: TasksSubTab; label: string; icon: React.ReactNode }[] = [
|
|
18
|
+
{ id: 'overview', label: '任务总览', icon: <Columns3 size={14} /> },
|
|
19
|
+
{ id: 'schedules', label: '定时任务', icon: <Calendar size={14} /> },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const COLUMNS: {
|
|
23
|
+
id: OverviewStatus;
|
|
24
|
+
title: string;
|
|
25
|
+
icon: React.ReactNode;
|
|
26
|
+
headerBg: string;
|
|
27
|
+
bodyBg: string;
|
|
28
|
+
}[] = [
|
|
29
|
+
{
|
|
30
|
+
id: 'pending',
|
|
31
|
+
title: 'TODO',
|
|
32
|
+
icon: <Circle size={14} className="shrink-0 text-[var(--color-text-muted)]" />,
|
|
33
|
+
headerBg: 'rgba(59, 130, 246, 0.22)',
|
|
34
|
+
bodyBg: 'rgba(59, 130, 246, 0.05)',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'in_progress',
|
|
38
|
+
title: 'IN PROGRESS',
|
|
39
|
+
icon: <Loader2 size={14} className="shrink-0 text-[var(--color-text-muted)]" />,
|
|
40
|
+
headerBg: 'rgba(234, 179, 8, 0.24)',
|
|
41
|
+
bodyBg: 'rgba(234, 179, 8, 0.06)',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'completed',
|
|
45
|
+
title: 'DONE',
|
|
46
|
+
icon: <CheckCircle2 size={14} className="shrink-0 text-[var(--color-text-muted)]" />,
|
|
47
|
+
headerBg: 'rgba(34, 197, 94, 0.22)',
|
|
48
|
+
bodyBg: 'rgba(34, 197, 94, 0.05)',
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
function isOverviewStatus(status: TeamTaskStatus): status is OverviewStatus {
|
|
53
|
+
return status === 'pending' || status === 'in_progress' || status === 'completed';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getTaskUpdatedAt(task: GlobalTask): number {
|
|
57
|
+
const raw = task.updatedAt ?? task.createdAt;
|
|
58
|
+
const time = raw ? new Date(raw).getTime() : 0;
|
|
59
|
+
return Number.isFinite(time) ? time : 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildOptionLabel(value: string | null | undefined, fallback: string): string {
|
|
63
|
+
const trimmed = value?.trim();
|
|
64
|
+
return trimmed && trimmed.length > 0 ? trimmed : fallback;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const TasksView = (): React.JSX.Element => {
|
|
68
|
+
const [activeTab, setActiveTab] = useState<TasksSubTab>('overview');
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className="flex h-full flex-col">
|
|
72
|
+
<div className="flex items-center border-b border-[var(--color-border)] px-4 pt-2">
|
|
73
|
+
{SUB_TABS.map((tab) => (
|
|
74
|
+
<button
|
|
75
|
+
key={tab.id}
|
|
76
|
+
onClick={() => setActiveTab(tab.id)}
|
|
77
|
+
className={cn(
|
|
78
|
+
'flex items-center gap-1.5 border-b-2 px-4 pb-2 text-sm font-medium transition-colors',
|
|
79
|
+
activeTab === tab.id
|
|
80
|
+
? 'border-[var(--color-primary)] text-[var(--color-text)]'
|
|
81
|
+
: 'border-transparent text-[var(--color-text-muted)] hover:text-[var(--color-text)]'
|
|
82
|
+
)}
|
|
83
|
+
>
|
|
84
|
+
{tab.icon}
|
|
85
|
+
{tab.label}
|
|
86
|
+
</button>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
<div className="flex-1 overflow-auto">
|
|
90
|
+
{activeTab === 'overview' && <TaskOverviewPool />}
|
|
91
|
+
{activeTab === 'schedules' && <SchedulesView />}
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const TaskOverviewPool = (): React.JSX.Element => {
|
|
98
|
+
const {
|
|
99
|
+
globalTasks,
|
|
100
|
+
globalTasksLoading,
|
|
101
|
+
globalTasksInitialized,
|
|
102
|
+
fetchAllTasks,
|
|
103
|
+
openGlobalTaskDetail,
|
|
104
|
+
} = useStore(
|
|
105
|
+
useShallow((s) => ({
|
|
106
|
+
globalTasks: s.globalTasks,
|
|
107
|
+
globalTasksLoading: s.globalTasksLoading,
|
|
108
|
+
globalTasksInitialized: s.globalTasksInitialized,
|
|
109
|
+
fetchAllTasks: s.fetchAllTasks,
|
|
110
|
+
openGlobalTaskDetail: s.openGlobalTaskDetail,
|
|
111
|
+
}))
|
|
112
|
+
);
|
|
113
|
+
const [teamFilter, setTeamFilter] = useState('all');
|
|
114
|
+
const [statusFilter, setStatusFilter] = useState<'all' | OverviewStatus>('all');
|
|
115
|
+
const [ownerFilter, setOwnerFilter] = useState('all');
|
|
116
|
+
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
void fetchAllTasks();
|
|
119
|
+
}, [fetchAllTasks]);
|
|
120
|
+
|
|
121
|
+
const overviewTasks = useMemo(
|
|
122
|
+
() => globalTasks.filter((task) => isOverviewStatus(task.status) && !task.teamDeleted),
|
|
123
|
+
[globalTasks]
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const teamOptions = useMemo(
|
|
127
|
+
() =>
|
|
128
|
+
Array.from(
|
|
129
|
+
new Map(overviewTasks.map((task) => [task.teamName, task.teamDisplayName])).entries()
|
|
130
|
+
).sort((a, b) => a[1].localeCompare(b[1])),
|
|
131
|
+
[overviewTasks]
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const ownerOptions = useMemo(() => {
|
|
135
|
+
const owners = new Set<string>();
|
|
136
|
+
for (const task of overviewTasks) {
|
|
137
|
+
if (task.owner?.trim()) owners.add(task.owner.trim());
|
|
138
|
+
}
|
|
139
|
+
return Array.from(owners).sort((a, b) => a.localeCompare(b));
|
|
140
|
+
}, [overviewTasks]);
|
|
141
|
+
|
|
142
|
+
const filteredTasks = useMemo(
|
|
143
|
+
() =>
|
|
144
|
+
overviewTasks
|
|
145
|
+
.filter((task) => teamFilter === 'all' || task.teamName === teamFilter)
|
|
146
|
+
.filter((task) => statusFilter === 'all' || task.status === statusFilter)
|
|
147
|
+
.filter((task) => ownerFilter === 'all' || task.owner === ownerFilter)
|
|
148
|
+
.sort((a, b) => getTaskUpdatedAt(b) - getTaskUpdatedAt(a)),
|
|
149
|
+
[overviewTasks, ownerFilter, statusFilter, teamFilter]
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const grouped = useMemo(() => {
|
|
153
|
+
const map = new Map<OverviewStatus, GlobalTask[]>();
|
|
154
|
+
for (const column of COLUMNS) {
|
|
155
|
+
map.set(column.id, []);
|
|
156
|
+
}
|
|
157
|
+
for (const task of filteredTasks) {
|
|
158
|
+
if (isOverviewStatus(task.status)) {
|
|
159
|
+
map.get(task.status)?.push(task);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return map;
|
|
163
|
+
}, [filteredTasks]);
|
|
164
|
+
|
|
165
|
+
const clearFilters = useCallback(() => {
|
|
166
|
+
setTeamFilter('all');
|
|
167
|
+
setStatusFilter('all');
|
|
168
|
+
setOwnerFilter('all');
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
if (globalTasksLoading && !globalTasksInitialized) {
|
|
172
|
+
return (
|
|
173
|
+
<div className="flex h-full items-center justify-center text-sm text-[var(--color-text-muted)]">
|
|
174
|
+
加载团队任务…
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<div className="flex h-full min-w-0 flex-col gap-3 p-4">
|
|
181
|
+
<div className="flex flex-wrap items-end gap-2">
|
|
182
|
+
<div className="min-w-[180px]">
|
|
183
|
+
<label
|
|
184
|
+
htmlFor="tasks-overview-team-filter"
|
|
185
|
+
className="mb-1 block text-[11px] font-medium text-[var(--color-text-muted)]"
|
|
186
|
+
>
|
|
187
|
+
团队
|
|
188
|
+
</label>
|
|
189
|
+
<select
|
|
190
|
+
id="tasks-overview-team-filter"
|
|
191
|
+
value={teamFilter}
|
|
192
|
+
onChange={(event) => setTeamFilter(event.target.value)}
|
|
193
|
+
className="h-8 w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 text-xs text-[var(--color-text)]"
|
|
194
|
+
>
|
|
195
|
+
<option value="all">全部团队</option>
|
|
196
|
+
{teamOptions.map(([teamName, displayName]) => (
|
|
197
|
+
<option key={teamName} value={teamName}>
|
|
198
|
+
{displayName}
|
|
199
|
+
</option>
|
|
200
|
+
))}
|
|
201
|
+
</select>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<div className="min-w-[160px]">
|
|
205
|
+
<label
|
|
206
|
+
htmlFor="tasks-overview-status-filter"
|
|
207
|
+
className="mb-1 block text-[11px] font-medium text-[var(--color-text-muted)]"
|
|
208
|
+
>
|
|
209
|
+
状态
|
|
210
|
+
</label>
|
|
211
|
+
<select
|
|
212
|
+
id="tasks-overview-status-filter"
|
|
213
|
+
value={statusFilter}
|
|
214
|
+
onChange={(event) => setStatusFilter(event.target.value as 'all' | OverviewStatus)}
|
|
215
|
+
className="h-8 w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 text-xs text-[var(--color-text)]"
|
|
216
|
+
>
|
|
217
|
+
<option value="all">全部状态</option>
|
|
218
|
+
<option value="pending">TODO</option>
|
|
219
|
+
<option value="in_progress">IN PROGRESS</option>
|
|
220
|
+
<option value="completed">DONE</option>
|
|
221
|
+
</select>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div className="min-w-[160px]">
|
|
225
|
+
<label
|
|
226
|
+
htmlFor="tasks-overview-owner-filter"
|
|
227
|
+
className="mb-1 block text-[11px] font-medium text-[var(--color-text-muted)]"
|
|
228
|
+
>
|
|
229
|
+
负责人
|
|
230
|
+
</label>
|
|
231
|
+
<select
|
|
232
|
+
id="tasks-overview-owner-filter"
|
|
233
|
+
value={ownerFilter}
|
|
234
|
+
onChange={(event) => setOwnerFilter(event.target.value)}
|
|
235
|
+
className="h-8 w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 text-xs text-[var(--color-text)]"
|
|
236
|
+
>
|
|
237
|
+
<option value="all">全部负责人</option>
|
|
238
|
+
{ownerOptions.map((owner) => (
|
|
239
|
+
<option key={owner} value={owner}>
|
|
240
|
+
{owner}
|
|
241
|
+
</option>
|
|
242
|
+
))}
|
|
243
|
+
</select>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
<Button variant="outline" size="sm" className="h-8 gap-1.5 text-xs" onClick={clearFilters}>
|
|
247
|
+
清空筛选
|
|
248
|
+
</Button>
|
|
249
|
+
<Button
|
|
250
|
+
variant="ghost"
|
|
251
|
+
size="sm"
|
|
252
|
+
className="ml-auto h-8 gap-1.5 text-xs text-[var(--color-text-muted)]"
|
|
253
|
+
onClick={() => void fetchAllTasks()}
|
|
254
|
+
>
|
|
255
|
+
<RefreshCw size={12} />
|
|
256
|
+
刷新
|
|
257
|
+
</Button>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div className="w-full min-w-0 max-w-full overflow-x-auto overflow-y-hidden pb-6">
|
|
261
|
+
<div className="grid min-w-[900px] grid-cols-3 items-start gap-3">
|
|
262
|
+
{COLUMNS.map((column) => {
|
|
263
|
+
const tasks = grouped.get(column.id) ?? [];
|
|
264
|
+
return (
|
|
265
|
+
<section
|
|
266
|
+
key={column.id}
|
|
267
|
+
className="relative rounded-md"
|
|
268
|
+
style={{ backgroundColor: column.bodyBg }}
|
|
269
|
+
>
|
|
270
|
+
{tasks.length > 0 ? (
|
|
271
|
+
<span className="absolute -right-2 -top-2 z-10 min-w-5 rounded-full bg-[var(--color-surface-raised)] px-1.5 py-0 text-center text-[10px] font-medium leading-5 text-[var(--color-text-secondary)] ring-1 ring-[var(--color-border)]">
|
|
272
|
+
{tasks.length}
|
|
273
|
+
</span>
|
|
274
|
+
) : null}
|
|
275
|
+
<header
|
|
276
|
+
className="rounded-t-md px-3 py-2"
|
|
277
|
+
style={{ backgroundColor: column.headerBg }}
|
|
278
|
+
>
|
|
279
|
+
<h4 className="flex items-center gap-2 text-xs font-semibold uppercase tracking-wide text-[var(--color-text)]">
|
|
280
|
+
{column.icon}
|
|
281
|
+
{column.title}
|
|
282
|
+
</h4>
|
|
283
|
+
</header>
|
|
284
|
+
<div className="flex flex-col gap-1.5 p-2">
|
|
285
|
+
{tasks.length === 0 ? (
|
|
286
|
+
<div className="rounded-md border border-dashed border-[var(--color-border)] p-3 text-xs text-[var(--color-text-muted)]">
|
|
287
|
+
No tasks
|
|
288
|
+
</div>
|
|
289
|
+
) : (
|
|
290
|
+
tasks.map((task) => (
|
|
291
|
+
<GlobalOverviewTaskCard
|
|
292
|
+
key={`${task.teamName}:${task.id}`}
|
|
293
|
+
task={task}
|
|
294
|
+
onOpen={() => openGlobalTaskDetail(task.teamName, task.id)}
|
|
295
|
+
/>
|
|
296
|
+
))
|
|
297
|
+
)}
|
|
298
|
+
</div>
|
|
299
|
+
</section>
|
|
300
|
+
);
|
|
301
|
+
})}
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const GlobalOverviewTaskCard = ({
|
|
309
|
+
task,
|
|
310
|
+
onOpen,
|
|
311
|
+
}: {
|
|
312
|
+
task: GlobalTask;
|
|
313
|
+
onOpen: () => void;
|
|
314
|
+
}): React.JSX.Element => {
|
|
315
|
+
const ownerLabel = buildOptionLabel(task.owner, '未分配');
|
|
316
|
+
const dispatchFrom = task.dispatchMeta?.originTeam;
|
|
317
|
+
const dispatchTo = task.dispatchMeta?.targetTeam;
|
|
318
|
+
return (
|
|
319
|
+
<button
|
|
320
|
+
type="button"
|
|
321
|
+
className="relative w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface-raised)] px-1.5 py-3 text-left text-xs transition-colors hover:border-[var(--color-border-emphasis)]"
|
|
322
|
+
onClick={onOpen}
|
|
323
|
+
>
|
|
324
|
+
<span className="absolute left-[3px] top-[2px] text-[9px] leading-none text-[var(--color-text-muted)]">
|
|
325
|
+
#{task.displayId ?? deriveTaskDisplayId(task.id)}
|
|
326
|
+
</span>
|
|
327
|
+
<div className="mb-2 pt-[11px]">
|
|
328
|
+
<h5 className="line-clamp-2 text-xs font-medium text-[var(--color-text)]">
|
|
329
|
+
{task.subject}
|
|
330
|
+
</h5>
|
|
331
|
+
{task.dispatchMeta ? (
|
|
332
|
+
<span className="mt-1 inline-flex items-center rounded-full bg-yellow-500/15 px-1.5 py-0.5 text-[10px] font-medium text-yellow-600 dark:text-yellow-400">
|
|
333
|
+
{dispatchFrom} 给 {dispatchTo} 派单
|
|
334
|
+
</span>
|
|
335
|
+
) : null}
|
|
336
|
+
</div>
|
|
337
|
+
<div className="flex flex-wrap items-center gap-1.5 text-[10px] text-[var(--color-text-muted)]">
|
|
338
|
+
<span className="rounded bg-white/5 px-1.5 py-0.5">{task.teamDisplayName}</span>
|
|
339
|
+
<span className="rounded bg-white/5 px-1.5 py-0.5">{ownerLabel}</span>
|
|
340
|
+
</div>
|
|
341
|
+
</button>
|
|
342
|
+
);
|
|
343
|
+
};
|
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
Terminal,
|
|
69
69
|
Trash2,
|
|
70
70
|
Loader2,
|
|
71
|
+
MessageSquare,
|
|
71
72
|
Users,
|
|
72
73
|
} from 'lucide-react';
|
|
73
74
|
import { useShallow } from 'zustand/react/shallow';
|
|
@@ -1591,6 +1592,24 @@ export const TeamDetailView = ({
|
|
|
1591
1592
|
openLaunchDialog('relaunch');
|
|
1592
1593
|
}, [openLaunchDialog]);
|
|
1593
1594
|
|
|
1595
|
+
const handleStartCcConnectTeam = useCallback(() => {
|
|
1596
|
+
void (async () => {
|
|
1597
|
+
if (!data?.config.projectPath) {
|
|
1598
|
+
openLaunchDialog('launch');
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
await api.ccSettings.restart();
|
|
1602
|
+
await api.teams.launchTeam({
|
|
1603
|
+
teamName,
|
|
1604
|
+
cwd: data.config.projectPath,
|
|
1605
|
+
});
|
|
1606
|
+
window.setTimeout(() => {
|
|
1607
|
+
void fetchTeams();
|
|
1608
|
+
void selectTeam(teamName);
|
|
1609
|
+
}, 1500);
|
|
1610
|
+
})();
|
|
1611
|
+
}, [data?.config.projectPath, fetchTeams, openLaunchDialog, selectTeam, teamName]);
|
|
1612
|
+
|
|
1594
1613
|
const handleLaunchDialogSubmit = useCallback(
|
|
1595
1614
|
async (request: TeamLaunchRequest): Promise<void> => {
|
|
1596
1615
|
await launchTeam(request);
|
|
@@ -1904,11 +1923,8 @@ export const TeamDetailView = ({
|
|
|
1904
1923
|
pendingRepliesByMember,
|
|
1905
1924
|
onPendingReplyChange: setPendingRepliesByMember,
|
|
1906
1925
|
onMemberClick: handleSelectMember,
|
|
1907
|
-
onTaskClick: handleOpenTask,
|
|
1908
|
-
onCreateTaskFromMessage: handleCreateTaskFromMessage,
|
|
1909
1926
|
onReplyToMessage: handleReplyToMessage,
|
|
1910
1927
|
onRestartTeam: handleRestartTeam,
|
|
1911
|
-
onTaskIdClick: handleTaskIdClick,
|
|
1912
1928
|
inlineScrollContainerRef: contentRef,
|
|
1913
1929
|
showPositionControls: false,
|
|
1914
1930
|
}),
|
|
@@ -1917,12 +1933,9 @@ export const TeamDetailView = ({
|
|
|
1917
1933
|
data?.config.leadSessionId,
|
|
1918
1934
|
data?.isAlive,
|
|
1919
1935
|
data?.tasks,
|
|
1920
|
-
handleCreateTaskFromMessage,
|
|
1921
|
-
handleOpenTask,
|
|
1922
1936
|
handleReplyToMessage,
|
|
1923
1937
|
handleRestartTeam,
|
|
1924
1938
|
handleSelectMember,
|
|
1925
|
-
handleTaskIdClick,
|
|
1926
1939
|
pendingRepliesByMember,
|
|
1927
1940
|
teamName,
|
|
1928
1941
|
teamSessionIds,
|
|
@@ -2245,10 +2258,7 @@ export const TeamDetailView = ({
|
|
|
2245
2258
|
</div>
|
|
2246
2259
|
|
|
2247
2260
|
{!data.isAlive && !isTeamProvisioning ? (
|
|
2248
|
-
<TeamOfflineStatusBanner
|
|
2249
|
-
teamName={teamName}
|
|
2250
|
-
onLaunch={() => openLaunchDialog('launch')}
|
|
2251
|
-
/>
|
|
2261
|
+
<TeamOfflineStatusBanner teamName={teamName} onLaunch={handleStartCcConnectTeam} />
|
|
2252
2262
|
) : null}
|
|
2253
2263
|
|
|
2254
2264
|
<div ref={provisioningBannerRef}>
|
|
@@ -2276,16 +2286,12 @@ export const TeamDetailView = ({
|
|
|
2276
2286
|
<TeamMemberListBridge
|
|
2277
2287
|
teamName={teamName}
|
|
2278
2288
|
members={membersWithLiveBranches}
|
|
2279
|
-
memberTaskCounts={memberTaskCounts}
|
|
2280
|
-
taskMap={taskMap}
|
|
2281
2289
|
pendingRepliesByMember={pendingRepliesByMember}
|
|
2282
2290
|
isTeamAlive={data.isAlive}
|
|
2283
2291
|
isTeamProvisioning={isTeamProvisioning}
|
|
2284
2292
|
launchParams={launchParams}
|
|
2285
2293
|
onMemberClick={handleSelectMember}
|
|
2286
2294
|
onSendMessage={handleSendMessageToMember}
|
|
2287
|
-
onAssignTask={handleAssignTaskToMember}
|
|
2288
|
-
onOpenTask={handleOpenTaskById}
|
|
2289
2295
|
onRestartMember={handleRestartMember}
|
|
2290
2296
|
onSkipMemberForLaunch={handleSkipMemberForLaunch}
|
|
2291
2297
|
/>
|
|
@@ -2412,7 +2418,6 @@ export const TeamDetailView = ({
|
|
|
2412
2418
|
const task = data?.tasks.find((t) => t.id === taskId);
|
|
2413
2419
|
await updateTaskStatus(teamName, taskId, 'pending');
|
|
2414
2420
|
|
|
2415
|
-
// Notify assignee directly via inbox — they'll see it immediately
|
|
2416
2421
|
if (task?.owner) {
|
|
2417
2422
|
try {
|
|
2418
2423
|
await api.teams.sendMessage(teamName, {
|
|
@@ -2425,7 +2430,6 @@ export const TeamDetailView = ({
|
|
|
2425
2430
|
}
|
|
2426
2431
|
}
|
|
2427
2432
|
|
|
2428
|
-
// Also notify team lead so they can reassign/coordinate
|
|
2429
2433
|
if (data?.isAlive) {
|
|
2430
2434
|
try {
|
|
2431
2435
|
const ownerSuffix = task?.owner
|
|
@@ -2556,16 +2560,7 @@ export const TeamDetailView = ({
|
|
|
2556
2560
|
setReplyQuote(undefined);
|
|
2557
2561
|
setSendDialogOpen(true);
|
|
2558
2562
|
}}
|
|
2559
|
-
onAssignTask={() => {
|
|
2560
|
-
const name = selectedMember?.name ?? '';
|
|
2561
|
-
closeSelectedMemberDialog();
|
|
2562
|
-
openCreateTaskDialog('', '', name);
|
|
2563
|
-
}}
|
|
2564
2563
|
onRestartMember={handleRestartMember}
|
|
2565
|
-
onTaskClick={(task) => {
|
|
2566
|
-
closeSelectedMemberDialog();
|
|
2567
|
-
setSelectedTask(task);
|
|
2568
|
-
}}
|
|
2569
2564
|
onUpdateRole={async (memberName, role) => {
|
|
2570
2565
|
setUpdatingRoleLoading(true);
|
|
2571
2566
|
try {
|
|
@@ -2598,22 +2593,6 @@ export const TeamDetailView = ({
|
|
|
2598
2593
|
}}
|
|
2599
2594
|
/>
|
|
2600
2595
|
|
|
2601
|
-
<CreateTaskDialog
|
|
2602
|
-
open={createTaskDialog.open}
|
|
2603
|
-
teamName={teamName}
|
|
2604
|
-
members={activeMembers}
|
|
2605
|
-
tasks={data.tasks}
|
|
2606
|
-
isTeamAlive={data.isAlive && !isTeamProvisioning}
|
|
2607
|
-
defaultSubject={createTaskDialog.defaultSubject}
|
|
2608
|
-
defaultDescription={createTaskDialog.defaultDescription}
|
|
2609
|
-
defaultOwner={createTaskDialog.defaultOwner}
|
|
2610
|
-
defaultStartImmediately={createTaskDialog.defaultStartImmediately}
|
|
2611
|
-
defaultChip={createTaskDialog.defaultChip}
|
|
2612
|
-
onClose={closeCreateTaskDialog}
|
|
2613
|
-
onSubmit={handleCreateTask}
|
|
2614
|
-
submitting={creatingTask}
|
|
2615
|
-
/>
|
|
2616
|
-
|
|
2617
2596
|
<EditTeamDialog
|
|
2618
2597
|
open={editDialogOpen}
|
|
2619
2598
|
teamName={teamName}
|
|
@@ -2806,63 +2785,6 @@ export const TeamDetailView = ({
|
|
|
2806
2785
|
}}
|
|
2807
2786
|
/>
|
|
2808
2787
|
|
|
2809
|
-
<TaskDetailDialog
|
|
2810
|
-
open={selectedTask !== null}
|
|
2811
|
-
task={selectedTask}
|
|
2812
|
-
teamName={teamName}
|
|
2813
|
-
kanbanTaskState={
|
|
2814
|
-
selectedTask ? data?.kanbanState.tasks[selectedTask.id] : undefined
|
|
2815
|
-
}
|
|
2816
|
-
taskMap={taskMap}
|
|
2817
|
-
members={activeMembers}
|
|
2818
|
-
onClose={() => setSelectedTask(null)}
|
|
2819
|
-
onScrollToTask={(taskId) => {
|
|
2820
|
-
setSelectedTask(null);
|
|
2821
|
-
const el = document.querySelector(`[data-task-id="${taskId}"]`);
|
|
2822
|
-
if (el) {
|
|
2823
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
2824
|
-
el.classList.remove('kanban-card-focus-pulse');
|
|
2825
|
-
void (el as HTMLElement).offsetWidth;
|
|
2826
|
-
el.classList.add('kanban-card-focus-pulse');
|
|
2827
|
-
el.addEventListener(
|
|
2828
|
-
'animationend',
|
|
2829
|
-
() => el.classList.remove('kanban-card-focus-pulse'),
|
|
2830
|
-
{ once: true }
|
|
2831
|
-
);
|
|
2832
|
-
}
|
|
2833
|
-
}}
|
|
2834
|
-
onOwnerChange={(taskId, owner) => {
|
|
2835
|
-
void (async () => {
|
|
2836
|
-
try {
|
|
2837
|
-
await updateTaskOwner(teamName, taskId, owner);
|
|
2838
|
-
} catch {
|
|
2839
|
-
// error via store
|
|
2840
|
-
}
|
|
2841
|
-
})();
|
|
2842
|
-
}}
|
|
2843
|
-
onViewChanges={handleViewChangesForFile}
|
|
2844
|
-
onOpenInEditor={(filePath) => {
|
|
2845
|
-
const { revealFileInEditor } = useStore.getState();
|
|
2846
|
-
revealFileInEditor(filePath);
|
|
2847
|
-
}}
|
|
2848
|
-
onDeleteTask={handleDeleteTask}
|
|
2849
|
-
/>
|
|
2850
|
-
|
|
2851
|
-
<TrashDialog
|
|
2852
|
-
open={trashOpen}
|
|
2853
|
-
tasks={deletedTasks}
|
|
2854
|
-
onClose={() => setTrashOpen(false)}
|
|
2855
|
-
onRestore={(taskId) => {
|
|
2856
|
-
void (async () => {
|
|
2857
|
-
try {
|
|
2858
|
-
await restoreTask(teamName, taskId);
|
|
2859
|
-
} catch {
|
|
2860
|
-
// error via store
|
|
2861
|
-
}
|
|
2862
|
-
})();
|
|
2863
|
-
}}
|
|
2864
|
-
/>
|
|
2865
|
-
|
|
2866
2788
|
<ChangeReviewDialog
|
|
2867
2789
|
open={reviewDialogState.open}
|
|
2868
2790
|
onOpenChange={(open) =>
|
|
@@ -2058,7 +2058,7 @@ export const LaunchTeamDialog = (props: LaunchTeamDialogProps): React.JSX.Elemen
|
|
|
2058
2058
|
) : effectiveTeamName ? (
|
|
2059
2059
|
`为团队“${effectiveTeamName}”创建自动运行计划`
|
|
2060
2060
|
) : (
|
|
2061
|
-
'
|
|
2061
|
+
'创建团队自动运行计划'
|
|
2062
2062
|
);
|
|
2063
2063
|
|
|
2064
2064
|
const submitLabel = isLaunchMode
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
FilePlus,
|
|
17
17
|
FolderOpen,
|
|
18
18
|
FolderPlus,
|
|
19
|
-
ListTodo,
|
|
20
19
|
MessageSquare,
|
|
21
20
|
Pencil,
|
|
22
21
|
Trash2,
|
|
@@ -39,8 +38,6 @@ interface EditorContextMenuProps {
|
|
|
39
38
|
onNewFolder: (parentDir: string) => void;
|
|
40
39
|
onDelete: (path: string) => void;
|
|
41
40
|
onRename: (path: string) => void;
|
|
42
|
-
/** Trigger "Create Task" with a file mention (files only, not directories) */
|
|
43
|
-
onCreateTask?: (filePath: string) => void;
|
|
44
41
|
/** Trigger "Write Teammate" with a file mention (files only, not directories) */
|
|
45
42
|
onSendMessage?: (filePath: string) => void;
|
|
46
43
|
}
|
|
@@ -56,7 +53,6 @@ export const EditorContextMenu = ({
|
|
|
56
53
|
onNewFolder,
|
|
57
54
|
onDelete,
|
|
58
55
|
onRename,
|
|
59
|
-
onCreateTask,
|
|
60
56
|
onSendMessage,
|
|
61
57
|
}: EditorContextMenuProps): React.ReactElement => {
|
|
62
58
|
const [target, setTarget] = useState<TargetEntry | null>(null);
|
|
@@ -183,27 +179,16 @@ export const EditorContextMenu = ({
|
|
|
183
179
|
)}
|
|
184
180
|
|
|
185
181
|
{/* Team actions — file only */}
|
|
186
|
-
{target && !target.isDir &&
|
|
182
|
+
{target && !target.isDir && onSendMessage && (
|
|
187
183
|
<>
|
|
188
184
|
<ContextMenu.Separator className="my-1 h-px bg-border" />
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
</ContextMenu.Item>
|
|
197
|
-
)}
|
|
198
|
-
{onCreateTask && (
|
|
199
|
-
<ContextMenu.Item
|
|
200
|
-
className="flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 text-xs text-text outline-none hover:bg-surface-raised focus:bg-surface-raised"
|
|
201
|
-
onSelect={() => onCreateTask(target.path)}
|
|
202
|
-
>
|
|
203
|
-
<ListTodo className="size-3.5 text-text-muted" />
|
|
204
|
-
Create Task
|
|
205
|
-
</ContextMenu.Item>
|
|
206
|
-
)}
|
|
185
|
+
<ContextMenu.Item
|
|
186
|
+
className="flex cursor-pointer items-center gap-2 rounded px-2 py-1.5 text-xs text-text outline-none hover:bg-surface-raised focus:bg-surface-raised"
|
|
187
|
+
onSelect={() => onSendMessage(target.path)}
|
|
188
|
+
>
|
|
189
|
+
<MessageSquare className="size-3.5 text-text-muted" />
|
|
190
|
+
Write Teammate
|
|
191
|
+
</ContextMenu.Item>
|
|
207
192
|
</>
|
|
208
193
|
)}
|
|
209
194
|
</ContextMenu.Content>
|
|
@@ -54,8 +54,6 @@ import type { FileTreeEntry, GitFileStatusType } from '@shared/types/editor';
|
|
|
54
54
|
interface EditorFileTreeProps {
|
|
55
55
|
selectedFilePath: string | null;
|
|
56
56
|
onFileSelect: (filePath: string) => void;
|
|
57
|
-
/** Trigger "Create Task" with a file mention from context menu */
|
|
58
|
-
onCreateTask?: (filePath: string) => void;
|
|
59
57
|
/** Trigger "Write Teammate" with a file mention from context menu */
|
|
60
58
|
onSendMessage?: (filePath: string) => void;
|
|
61
59
|
}
|
|
@@ -91,7 +89,6 @@ let fileTreeRenderCount = 0;
|
|
|
91
89
|
export const EditorFileTree = ({
|
|
92
90
|
selectedFilePath,
|
|
93
91
|
onFileSelect,
|
|
94
|
-
onCreateTask,
|
|
95
92
|
onSendMessage,
|
|
96
93
|
}: EditorFileTreeProps): React.ReactElement => {
|
|
97
94
|
fileTreeRenderCount++;
|
|
@@ -452,7 +449,6 @@ export const EditorFileTree = ({
|
|
|
452
449
|
onNewFolder={handleNewFolder}
|
|
453
450
|
onDelete={handleDelete}
|
|
454
451
|
onRename={handleRename}
|
|
455
|
-
onCreateTask={onCreateTask}
|
|
456
452
|
onSendMessage={onSendMessage}
|
|
457
453
|
>
|
|
458
454
|
<DndContext
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { Button } from '@renderer/components/ui/button';
|
|
9
9
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
|
|
10
|
-
import {
|
|
10
|
+
import { MessageSquare } from 'lucide-react';
|
|
11
11
|
|
|
12
12
|
import type { EditorSelectionInfo } from '@shared/types/editor';
|
|
13
13
|
|
|
@@ -20,7 +20,6 @@ interface EditorSelectionMenuProps {
|
|
|
20
20
|
/** Bounding rect of the editor content container (for viewport → container conversion) */
|
|
21
21
|
containerRect: DOMRect;
|
|
22
22
|
onSendMessage: () => void;
|
|
23
|
-
onCreateTask: () => void;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
// =============================================================================
|
|
@@ -39,7 +38,6 @@ export const EditorSelectionMenu = ({
|
|
|
39
38
|
info,
|
|
40
39
|
containerRect,
|
|
41
40
|
onSendMessage,
|
|
42
|
-
onCreateTask,
|
|
43
41
|
}: EditorSelectionMenuProps): React.ReactElement | null => {
|
|
44
42
|
if (!info.text.trim()) return null;
|
|
45
43
|
|
|
@@ -71,11 +69,6 @@ export const EditorSelectionMenu = ({
|
|
|
71
69
|
label="Write Teammate"
|
|
72
70
|
onClick={onSendMessage}
|
|
73
71
|
/>
|
|
74
|
-
<MenuButton
|
|
75
|
-
icon={<ListTodo className="size-3.5" />}
|
|
76
|
-
label="Create Task"
|
|
77
|
-
onClick={onCreateTask}
|
|
78
|
-
/>
|
|
79
72
|
</div>
|
|
80
73
|
);
|
|
81
74
|
};
|