@taskforcehq/taskforce 0.3.306 → 0.3.307
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 +0 -2
- package/dist/Taskforce.module.css +317 -27
- package/dist/TaskforceCore.js +1 -0
- package/dist/TaskforceCore.test.js +178 -0
- package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
- package/dist/components/activity/EntityActivityTimeline.js +81 -0
- package/dist/components/context/ContextAttachmentManager.js +15 -3
- package/dist/components/features/AgentsModule.d.ts +5 -1
- package/dist/components/features/AgentsModule.js +142 -39
- package/dist/components/features/AgentsModule.test.js +260 -11
- package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
- package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
- package/dist/components/features/DocumentIndex.d.ts +2 -2
- package/dist/components/features/DocumentIndex.js +3 -4
- package/dist/components/features/DocumentViewer.d.ts +3 -1
- package/dist/components/features/DocumentViewer.js +6 -5
- package/dist/components/features/DocumentWorkspace.d.ts +10 -2
- package/dist/components/features/DocumentWorkspace.js +15 -4
- package/dist/components/features/DocumentWorkspace.test.js +20 -3
- package/dist/components/features/TaskSettings.d.ts +2 -0
- package/dist/components/features/TaskSettings.js +83 -78
- package/dist/components/features/TaskSettings.test.js +171 -82
- package/dist/components/task/TaskActionHeader.js +11 -5
- package/dist/components/task/TaskActionHeader.test.js +10 -0
- package/dist/components/task/TaskCard.js +6 -2
- package/dist/components/task/TaskContextUpload.test.js +3 -3
- package/dist/components/task/TaskForm.js +14 -64
- package/dist/components/task/TaskKanban.js +0 -8
- package/dist/components/task/TaskList.js +5 -1
- package/dist/components/task/TaskList.test.js +4 -0
- package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
- package/dist/components/ui/HierarchyActionBadge.js +5 -0
- package/dist/components/views/AppShellHeader.d.ts +3 -1
- package/dist/components/views/AppShellHeader.js +27 -11
- package/dist/components/views/AppShellHeader.test.js +18 -1
- package/dist/components/views/PlanComparisonPage.js +9 -1
- package/dist/components/views/PlanComparisonPage.test.js +19 -0
- package/dist/components/views/PlansPage.js +1 -1
- package/dist/components/views/StandaloneLayout.js +256 -69
- package/dist/components/views/WidgetView.js +10 -8
- package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
- package/dist/components/views/panels/PlanningDrawer.js +259 -51
- package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
- package/dist/config/envSchema.js +1 -0
- package/dist/core/AiProfileService.d.ts +7 -0
- package/dist/core/AiProfileService.js +460 -35
- package/dist/core/AiProfiles.test.js +648 -2
- package/dist/core/AttachmentLinkService.d.ts +3 -0
- package/dist/core/AttachmentLinkService.js +58 -13
- package/dist/core/GlobalSettingsService.js +12 -0
- package/dist/core/McpTokenService.d.ts +12 -0
- package/dist/core/McpTokenService.js +44 -1
- package/dist/core/PlanningEntities.test.js +366 -4
- package/dist/core/TaskActivityService.d.ts +28 -1
- package/dist/core/TaskActivityService.js +227 -46
- package/dist/core/TaskActivitySync.test.d.ts +1 -0
- package/dist/core/TaskActivitySync.test.js +90 -0
- package/dist/core/TaskAuditTimeline.test.js +96 -0
- package/dist/core/Taskforce.d.ts +190 -26
- package/dist/core/Taskforce.ids.test.js +58 -0
- package/dist/core/Taskforce.js +775 -71
- package/dist/core/Taskforce.mcpAuth.test.js +109 -0
- package/dist/core/types.d.ts +7 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
- package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
- package/dist/hooks/bootstrapTimeouts.js +8 -0
- package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
- package/dist/hooks/bootstrapTimeouts.test.js +12 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
- package/dist/hooks/sync/orchestratorShared.js +61 -1
- package/dist/hooks/sync/recovery.js +8 -0
- package/dist/hooks/sync/recovery.test.js +5 -5
- package/dist/hooks/sync/transfers.d.ts +2 -1
- package/dist/hooks/sync/transfers.js +36 -9
- package/dist/hooks/sync/transfers.test.js +534 -2
- package/dist/hooks/useFilterSort.js +13 -13
- package/dist/hooks/useFilterSort.test.js +87 -1
- package/dist/hooks/useSyncOrchestrator.js +50 -5
- package/dist/hooks/useTaskforce.d.ts +85 -50
- package/dist/hooks/useTaskforce.js +44 -14
- package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
- package/dist/hooks/useWorkspaceSyncController.js +7 -1
- package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
- package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
- package/dist/mcp/canonicalAssetHelpers.js +18 -10
- package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
- package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
- package/dist/mcp/clientIntegrations.d.ts +1 -0
- package/dist/mcp/clientIntegrations.js +25 -0
- package/dist/mcp/clientIntegrations.test.js +14 -3
- package/dist/mcp/clientProfileDefaults.js +3 -3
- package/dist/mcp/collaborationRegistrar.js +7 -7
- package/dist/mcp/documentAssetRegistrar.js +113 -7
- package/dist/mcp/runtime.d.ts +1 -0
- package/dist/mcp/runtime.js +969 -122
- package/dist/mcp/runtime.test.js +1083 -11
- package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
- package/dist/mcp/taskPlanningRegistrar.js +48 -0
- package/dist/mcp/toolRegistry.d.ts +56 -0
- package/dist/mcp/toolRegistry.js +8 -0
- package/dist/mcp/toolRegistry.test.js +4 -0
- package/dist/migrations/taskSchemaMigrations.js +21 -0
- package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
- package/dist/server/aiProfileAvatarAssets.js +319 -0
- package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
- package/dist/server/aiProfileAvatarAssets.test.js +213 -0
- package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
- package/dist/server/index.js +4 -0
- package/dist/server/index.test.js +6 -1
- package/dist/server/routes/admin.js +187 -9
- package/dist/server/routes/annotatedAttachments.js +51 -0
- package/dist/server/routes/auth.d.ts +11 -0
- package/dist/server/routes/auth.js +189 -27
- package/dist/server/routes/authSupport.d.ts +4 -0
- package/dist/server/routes/authSupport.js +12 -1
- package/dist/server/routes/billing.js +11 -0
- package/dist/server/routes/billing.test.js +3 -0
- package/dist/server/routes/documents.js +10 -0
- package/dist/server/routes/shared.js +0 -10
- package/dist/server/routes/sync.integration.test.js +221 -0
- package/dist/server/routes/sync.js +13 -3
- package/dist/server/routes/syncSnapshotRoutes.js +2 -0
- package/dist/server/routes/tasks.js +12 -9
- package/dist/server/routes.js +87 -8
- package/dist/server/routes.test.js +890 -22
- package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
- package/dist/shared/aiProfileSurfaceType.js +10 -8
- package/dist/storage/objectStorage.d.ts +1 -0
- package/dist/storage/objectStorage.js +2 -1
- package/dist/storage/objectStorageClient.d.ts +1 -0
- package/dist/storage/objectStorageClient.js +41 -8
- package/dist/storage/objectStorageClient.test.js +26 -1
- package/dist/storage/objectStorageConfig.js +3 -1
- package/dist/storage/objectStorageConfig.test.js +13 -1
- package/dist/sync/collaborationSyncPayload.js +2 -0
- package/dist/sync/collaborationSyncPayload.test.js +1 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -3
- package/dist/sync/collaborationSyncState.js +19 -12
- package/dist/sync/contentSyncPayload.d.ts +5 -1
- package/dist/sync/contentSyncPayload.js +9 -0
- package/dist/sync/contentSyncPayload.test.js +39 -0
- package/dist/sync/contentSyncState.d.ts +5 -1
- package/dist/sync/contentSyncState.js +231 -37
- package/dist/sync/planningSyncPayload.js +5 -0
- package/dist/sync/planningSyncPayload.test.js +24 -0
- package/dist/sync/syncApplyHandlers.d.ts +4 -4
- package/dist/sync/syncApplyHandlers.js +89 -31
- package/dist/sync/syncApplyHandlers.test.js +184 -4
- package/dist/sync/syncResourceAdapters.d.ts +2 -0
- package/dist/sync/syncResourceAdapters.js +2 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +13 -1
- package/dist/sync/syncService.test.js +39 -0
- package/dist/sync/workspacePullFeed.d.ts +4 -0
- package/dist/sync/workspacePullFeed.js +129 -70
- package/dist/sync/workspacePullFeed.test.js +49 -0
- package/dist/sync/workspaceSyncModel.d.ts +28 -6
- package/dist/sync/workspaceSyncModel.js +159 -45
- package/dist/sync/workspaceSyncModel.test.js +269 -3
- package/dist/types.d.ts +33 -2
- package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
- package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
- package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
- package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
- package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
- package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-D4XseTwW.js} +1 -1
- package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
- package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
- package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
- package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-oOA9bcdK.js} +4 -4
- package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
- package/dist/ui/assets/index-607WPo_X.js +4 -0
- package/dist/ui/assets/index-CYyodXsg.css +1 -0
- package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/ui/product/agent manager.png +0 -0
- package/dist/ui/product/agent_manager_02.png +0 -0
- package/dist/utils/aiProfileEvents.d.ts +7 -0
- package/dist/utils/aiProfileEvents.js +6 -0
- package/dist/utils/assignees.d.ts +6 -0
- package/dist/utils/assignees.js +8 -1
- package/dist/utils/assignees.test.d.ts +1 -0
- package/dist/utils/assignees.test.js +25 -0
- package/dist/utils/avatarDisplay.d.ts +1 -0
- package/dist/utils/avatarDisplay.js +15 -0
- package/dist/utils/contextFiles.d.ts +3 -1
- package/dist/utils/contextFiles.js +2 -1
- package/dist/utils/taskActivity.d.ts +7 -1
- package/dist/utils/taskActivity.js +63 -1
- package/dist/utils/taskActivity.test.js +23 -1
- package/dist/utils/taskSearch.d.ts +22 -0
- package/dist/utils/taskSearch.js +87 -0
- package/dist/utils/taskSearch.test.d.ts +1 -0
- package/dist/utils/taskSearch.test.js +69 -0
- package/dist/utils/taskUtils.js +10 -3
- package/dist/utils/taskUtils.test.d.ts +1 -0
- package/dist/utils/taskUtils.test.js +19 -0
- package/package.json +1 -1
- package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
- package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
- package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
- package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
- package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
- package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
- package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
- package/dist/ui/assets/index-C6k75jr8.js +0 -6
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
-
import { AlertTriangle, Bot, Check, Cloud, LaptopMinimal, Loader2 } from 'lucide-react';
|
|
3
|
+
import { AlertTriangle, Bot, Check, ChevronLeft, Cloud, LaptopMinimal, Loader2, Plug } from 'lucide-react';
|
|
4
4
|
import styles from '../../Taskforce.module.css';
|
|
5
5
|
import { EditableAvatarButton } from '../ui/EditableAvatarButton';
|
|
6
6
|
import { AvatarImageManagerModal } from '../views/standalone/modals/AvatarImageManagerModal';
|
|
7
7
|
import { prepareAvatarUploadFile } from '../../utils/avatarUpload';
|
|
8
|
-
import {
|
|
8
|
+
import { buildVersionedAvatarUrl } from '../../utils/avatarDisplay';
|
|
9
|
+
import { dispatchTaskforceAiProfilesMutated } from '../../utils/aiProfileEvents';
|
|
10
|
+
import { AI_PROFILE_SURFACE_TYPE_SECTION_ORDER, AI_PROFILE_SURFACE_TYPES, formatAiProfileSurfaceTypeLabel, formatAiProfileSurfaceTypeSectionLabel, getAiProfileSurfaceTypeSection, parseAiProfileSurfaceType } from '../../shared/aiProfileSurfaceType.js';
|
|
9
11
|
import { formatAiProfileSeatScopeLabel, parseAiProfileSeatScope } from '../../shared/aiProfileSeatScope.js';
|
|
12
|
+
function hasAiProfileAvatar(profile) {
|
|
13
|
+
return typeof profile.avatarUrl === 'string' && profile.avatarUrl.trim().length > 0;
|
|
14
|
+
}
|
|
15
|
+
function getAiProfileAvatarDisplayUrl(profile) {
|
|
16
|
+
if (!profile)
|
|
17
|
+
return '';
|
|
18
|
+
return buildVersionedAvatarUrl(profile.avatarUrl, profile.avatarRevision, profile.avatarUpdatedAt);
|
|
19
|
+
}
|
|
20
|
+
function getAiProfileAvatarSourceUrl(profile) {
|
|
21
|
+
if (!profile)
|
|
22
|
+
return '';
|
|
23
|
+
return buildVersionedAvatarUrl(profile.avatarSourceUrl, profile.avatarRevision, profile.avatarUpdatedAt);
|
|
24
|
+
}
|
|
25
|
+
function selectAiProfileGroupPrimary(profiles) {
|
|
26
|
+
return profiles.find(hasAiProfileAvatar) || profiles[0];
|
|
27
|
+
}
|
|
28
|
+
function buildAiProfileGroupKey(profile) {
|
|
29
|
+
return `${profile.seatScope || 'unknown'}:${profile.name.trim().toLowerCase()}`;
|
|
30
|
+
}
|
|
31
|
+
function formatDuplicateCount(count) {
|
|
32
|
+
const duplicates = Math.max(0, count - 1);
|
|
33
|
+
return `${duplicates} duplicate${duplicates === 1 ? '' : 's'}`;
|
|
34
|
+
}
|
|
10
35
|
function parseAiProfileSeatUsage(value) {
|
|
11
36
|
if (!value || typeof value !== 'object')
|
|
12
37
|
return null;
|
|
@@ -34,13 +59,15 @@ function renderAiProfileSeatScopeIcon(seatScope, options) {
|
|
|
34
59
|
}
|
|
35
60
|
return null;
|
|
36
61
|
}
|
|
37
|
-
export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSessionResolved = false, isAuthenticated = false, cloudAiProfileSeatUsage = null, }) {
|
|
62
|
+
export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSessionResolved = false, isAuthenticated = false, cloudAiProfileSeatUsage = null, agentTrayOpen = false, onCloseAgentTray, mcpSettingsNode = null, }) {
|
|
38
63
|
const [aiProfiles, setAiProfiles] = useState([]);
|
|
39
64
|
const [localAiProfileSeatUsage, setLocalAiProfileSeatUsage] = useState(null);
|
|
40
65
|
const [aiProfilesLoading, setAiProfilesLoading] = useState(false);
|
|
41
66
|
const [aiProfileMergeState, setAiProfileMergeState] = useState(null);
|
|
42
67
|
const [aiProfileMergeStatus, setAiProfileMergeStatus] = useState(null);
|
|
43
68
|
const [aiProfileArchiveError, setAiProfileArchiveError] = useState(null);
|
|
69
|
+
const [aiProfileCategoryError, setAiProfileCategoryError] = useState(null);
|
|
70
|
+
const [aiProfileCategoryBusy, setAiProfileCategoryBusy] = useState(false);
|
|
44
71
|
const [selectedGroupId, setSelectedGroupId] = useState(null);
|
|
45
72
|
const [avatarEditorProfileId, setAvatarEditorProfileId] = useState(null);
|
|
46
73
|
const [aiAvatarBusy, setAiAvatarBusy] = useState(false);
|
|
@@ -50,7 +77,7 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
50
77
|
return;
|
|
51
78
|
setAiProfilesLoading(true);
|
|
52
79
|
try {
|
|
53
|
-
const rosterUrl = `/api/taskforce/workspace/assignee-options?workspaceId=${encodeURIComponent(workspaceId)}`;
|
|
80
|
+
const rosterUrl = `/api/taskforce/workspace/assignee-options?workspaceId=${encodeURIComponent(workspaceId)}&kind=agent`;
|
|
54
81
|
const rosterResponse = await fetch(rosterUrl, {
|
|
55
82
|
credentials: 'include',
|
|
56
83
|
});
|
|
@@ -69,6 +96,8 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
69
96
|
color: String(assignee.color || '#6B7280'),
|
|
70
97
|
avatarUrl: typeof assignee.avatarUrl === 'string' ? assignee.avatarUrl : null,
|
|
71
98
|
avatarSourceUrl: typeof assignee.avatarSourceUrl === 'string' ? assignee.avatarSourceUrl : null,
|
|
99
|
+
avatarRevision: Number.isFinite(Number(assignee.avatarRevision)) ? Math.max(0, Math.floor(Number(assignee.avatarRevision))) : 0,
|
|
100
|
+
avatarUpdatedAt: typeof assignee.avatarUpdatedAt === 'string' ? assignee.avatarUpdatedAt : null,
|
|
72
101
|
kind: String(assignee.kind || 'agent'),
|
|
73
102
|
description: typeof assignee.description === 'string' ? assignee.description : null,
|
|
74
103
|
role: typeof assignee.role === 'string' ? assignee.role : null,
|
|
@@ -87,6 +116,9 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
87
116
|
setAiProfileArchiveError((current) => (current && !agents.some((agent) => agent.id === current.profileId)
|
|
88
117
|
? null
|
|
89
118
|
: current));
|
|
119
|
+
setAiProfileCategoryError((current) => (current && !agents.some((agent) => agent.id === current.profileId)
|
|
120
|
+
? null
|
|
121
|
+
: current));
|
|
90
122
|
}
|
|
91
123
|
finally {
|
|
92
124
|
setAiProfilesLoading(false);
|
|
@@ -102,7 +134,7 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
102
134
|
if (!bySection.has(section))
|
|
103
135
|
bySection.set(section, new Map());
|
|
104
136
|
const sectionEntries = bySection.get(section);
|
|
105
|
-
const key = profile
|
|
137
|
+
const key = buildAiProfileGroupKey(profile);
|
|
106
138
|
if (!sectionEntries.has(key))
|
|
107
139
|
sectionEntries.set(key, []);
|
|
108
140
|
sectionEntries.get(key).push(profile);
|
|
@@ -116,7 +148,7 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
116
148
|
section,
|
|
117
149
|
sectionLabel: formatAiProfileSurfaceTypeSectionLabel(section),
|
|
118
150
|
profiles,
|
|
119
|
-
primaryProfile: profiles
|
|
151
|
+
primaryProfile: selectAiProfileGroupPrimary(profiles)
|
|
120
152
|
}))
|
|
121
153
|
}))
|
|
122
154
|
.filter((entry) => entry.groups.length > 0);
|
|
@@ -152,6 +184,7 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
152
184
|
setAiProfileMergeState(null);
|
|
153
185
|
setAiProfileMergeStatus({ type: 'success', message: 'AI profiles merged.' });
|
|
154
186
|
await loadAiProfiles();
|
|
187
|
+
dispatchTaskforceAiProfilesMutated({ workspaceId, profileId: keepId, reason: 'merge' });
|
|
155
188
|
}
|
|
156
189
|
catch {
|
|
157
190
|
setAiProfileMergeStatus({ type: 'error', message: 'Failed to merge AI profiles.' });
|
|
@@ -183,6 +216,7 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
183
216
|
}
|
|
184
217
|
setAiProfileMergeStatus({ type: 'success', message: 'AI profile removed from active roster.' });
|
|
185
218
|
await loadAiProfiles();
|
|
219
|
+
dispatchTaskforceAiProfilesMutated({ workspaceId, profileId: profile.id, reason: 'archive' });
|
|
186
220
|
}
|
|
187
221
|
catch {
|
|
188
222
|
setAiProfileArchiveError({
|
|
@@ -191,6 +225,61 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
191
225
|
});
|
|
192
226
|
}
|
|
193
227
|
};
|
|
228
|
+
const handleCategoryChange = async (group, value) => {
|
|
229
|
+
const nextSurfaceType = parseAiProfileSurfaceType(value);
|
|
230
|
+
const currentSurfaceTypes = new Set(group.profiles.map((profile) => profile.surfaceType ?? ''));
|
|
231
|
+
const nextSurfaceTypeKey = nextSurfaceType ?? '';
|
|
232
|
+
if (currentSurfaceTypes.size === 1 && currentSurfaceTypes.has(nextSurfaceTypeKey))
|
|
233
|
+
return;
|
|
234
|
+
setAiProfileCategoryBusy(true);
|
|
235
|
+
setAiProfileCategoryError(null);
|
|
236
|
+
setAiProfileMergeStatus(null);
|
|
237
|
+
const updatedProfiles = [];
|
|
238
|
+
try {
|
|
239
|
+
for (const profile of group.profiles) {
|
|
240
|
+
const res = await fetch('/api/taskforce/workspace/ai-profiles/surface-type', {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
credentials: 'include',
|
|
243
|
+
headers: { 'Content-Type': 'application/json' },
|
|
244
|
+
body: JSON.stringify({
|
|
245
|
+
profileId: profile.id,
|
|
246
|
+
surfaceType: nextSurfaceType
|
|
247
|
+
}),
|
|
248
|
+
});
|
|
249
|
+
const data = await res.json().catch(() => ({}));
|
|
250
|
+
if (!res.ok || !data?.profile) {
|
|
251
|
+
throw new Error(String(data?.error || 'Failed to update AI profile category.'));
|
|
252
|
+
}
|
|
253
|
+
const updated = data.profile;
|
|
254
|
+
updatedProfiles.push({
|
|
255
|
+
...profile,
|
|
256
|
+
surfaceType: parseAiProfileSurfaceType(updated.surfaceType),
|
|
257
|
+
updatedAt: typeof updated.updatedAt === 'string' ? updated.updatedAt : profile.updatedAt
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
const updatedById = new Map(updatedProfiles.map((profile) => [profile.id, profile]));
|
|
261
|
+
setAiProfiles((current) => current.map((profile) => updatedById.get(profile.id) || profile));
|
|
262
|
+
const nextPrimary = updatedById.get(group.primaryProfile.id) || {
|
|
263
|
+
...group.primaryProfile,
|
|
264
|
+
surfaceType: nextSurfaceType
|
|
265
|
+
};
|
|
266
|
+
const nextSection = getAiProfileSurfaceTypeSection(nextPrimary.surfaceType);
|
|
267
|
+
setSelectedGroupId(`${nextSection}:${buildAiProfileGroupKey(nextPrimary)}`);
|
|
268
|
+
setAiProfileMergeStatus({ type: 'success', message: 'AI profile category updated.' });
|
|
269
|
+
for (const profile of updatedProfiles) {
|
|
270
|
+
dispatchTaskforceAiProfilesMutated({ workspaceId, profileId: profile.id, reason: 'update' });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
setAiProfileCategoryError({
|
|
275
|
+
profileId: group.primaryProfile.id,
|
|
276
|
+
message: String(error?.message || 'Failed to update AI profile category.')
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
finally {
|
|
280
|
+
setAiProfileCategoryBusy(false);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
194
283
|
const fileToDataUrl = (file) => new Promise((resolve, reject) => {
|
|
195
284
|
const reader = new FileReader();
|
|
196
285
|
reader.onload = () => resolve(String(reader.result || ''));
|
|
@@ -206,46 +295,60 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
206
295
|
...profile,
|
|
207
296
|
avatarUrl: typeof updatedProfile.avatarUrl === 'string' ? updatedProfile.avatarUrl : null,
|
|
208
297
|
avatarSourceUrl: typeof updatedProfile.avatarSourceUrl === 'string' ? updatedProfile.avatarSourceUrl : null,
|
|
298
|
+
avatarRevision: Number.isFinite(Number(updatedProfile.avatarRevision)) ? Math.max(0, Math.floor(Number(updatedProfile.avatarRevision))) : profile.avatarRevision,
|
|
299
|
+
avatarUpdatedAt: typeof updatedProfile.avatarUpdatedAt === 'string' ? updatedProfile.avatarUpdatedAt : profile.avatarUpdatedAt,
|
|
209
300
|
updatedAt: typeof updatedProfile.updatedAt === 'string' ? updatedProfile.updatedAt : profile.updatedAt
|
|
210
301
|
}
|
|
211
302
|
: profile)));
|
|
212
303
|
};
|
|
213
|
-
const
|
|
214
|
-
const
|
|
304
|
+
const uploadAiProfileAvatar = async (profileId, displayFile, sourceFile) => {
|
|
305
|
+
const displayDataUrl = await fileToDataUrl(displayFile);
|
|
306
|
+
const sourceDataUrl = sourceFile ? await fileToDataUrl(sourceFile) : null;
|
|
307
|
+
const payload = {
|
|
308
|
+
profileId,
|
|
309
|
+
displayImage: {
|
|
310
|
+
dataUrl: displayDataUrl,
|
|
311
|
+
mimeType: displayFile.type || 'application/octet-stream',
|
|
312
|
+
originalName: displayFile.name || 'display-avatar'
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
if (sourceFile && sourceDataUrl) {
|
|
316
|
+
payload.sourceImage = {
|
|
317
|
+
dataUrl: sourceDataUrl,
|
|
318
|
+
mimeType: sourceFile.type || 'application/octet-stream',
|
|
319
|
+
originalName: sourceFile.name || 'source-avatar'
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const res = await fetch('/api/taskforce/workspace/ai-profiles/avatar/upload', {
|
|
215
323
|
method: 'POST',
|
|
216
324
|
credentials: 'include',
|
|
217
325
|
headers: { 'Content-Type': 'application/json' },
|
|
218
|
-
body: JSON.stringify(
|
|
219
|
-
profileId,
|
|
220
|
-
avatarUrl,
|
|
221
|
-
...(avatarSourceUrl !== undefined ? { avatarSourceUrl } : {})
|
|
222
|
-
}),
|
|
326
|
+
body: JSON.stringify(payload),
|
|
223
327
|
});
|
|
224
328
|
const data = await res.json().catch(() => ({}));
|
|
225
329
|
if (!res.ok || !data?.profile) {
|
|
226
330
|
throw new Error(String(data?.error || 'Failed to update AI profile avatar.'));
|
|
227
331
|
}
|
|
228
332
|
applyAiProfileAvatarUpdate(data.profile);
|
|
333
|
+
dispatchTaskforceAiProfilesMutated({ workspaceId, profileId, reason: 'avatar' });
|
|
229
334
|
};
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
const uploadRes = await fetch('/api/taskforce/context-upload', {
|
|
335
|
+
const clearAiProfileAvatar = async (profileId) => {
|
|
336
|
+
const res = await fetch('/api/taskforce/workspace/ai-profiles/avatar', {
|
|
233
337
|
method: 'POST',
|
|
234
338
|
credentials: 'include',
|
|
235
339
|
headers: { 'Content-Type': 'application/json' },
|
|
236
340
|
body: JSON.stringify({
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
folderPath: `ai-profiles/${profile.id}/avatar`
|
|
341
|
+
profileId,
|
|
342
|
+
avatarUrl: null,
|
|
343
|
+
avatarSourceUrl: null
|
|
241
344
|
})
|
|
242
345
|
});
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
throw new Error(String(uploadData?.error || 'Failed to upload AI profile photo.'));
|
|
346
|
+
const data = await res.json().catch(() => ({}));
|
|
347
|
+
if (!res.ok || !data?.profile) {
|
|
348
|
+
throw new Error(String(data?.error || 'Failed to update AI profile avatar.'));
|
|
247
349
|
}
|
|
248
|
-
|
|
350
|
+
applyAiProfileAvatarUpdate(data.profile);
|
|
351
|
+
dispatchTaskforceAiProfilesMutated({ workspaceId, profileId, reason: 'avatar' });
|
|
249
352
|
};
|
|
250
353
|
const handleAiProfileAvatarSelected = async (file, sourceFile) => {
|
|
251
354
|
if (!avatarEditorProfile)
|
|
@@ -264,8 +367,7 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
264
367
|
: 'AI profile photo must be 5 MB or smaller.');
|
|
265
368
|
}
|
|
266
369
|
const uploadFile = preparedAvatar.file;
|
|
267
|
-
|
|
268
|
-
let avatarSourceUrl;
|
|
370
|
+
let preparedSourceFile = null;
|
|
269
371
|
if (sourceFile) {
|
|
270
372
|
const preparedSource = await prepareAvatarUploadFile(sourceFile, { maxBytes: 5 * 1024 * 1024 });
|
|
271
373
|
if (preparedSource.exceededLimit) {
|
|
@@ -273,11 +375,9 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
273
375
|
? 'Animated GIF AI profile photos must be 5 MB or smaller.'
|
|
274
376
|
: 'AI profile source photo must be 5 MB or smaller.');
|
|
275
377
|
}
|
|
276
|
-
|
|
277
|
-
? avatarUrl
|
|
278
|
-
: await uploadAiProfileAvatarFile(preparedSource.file, avatarEditorProfile, 'source');
|
|
378
|
+
preparedSourceFile = preparedSource.file;
|
|
279
379
|
}
|
|
280
|
-
await
|
|
380
|
+
await uploadAiProfileAvatar(avatarEditorProfile.id, uploadFile, preparedSourceFile);
|
|
281
381
|
return true;
|
|
282
382
|
}
|
|
283
383
|
catch (error) {
|
|
@@ -294,7 +394,7 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
294
394
|
setAiAvatarBusy(true);
|
|
295
395
|
setAiAvatarError(null);
|
|
296
396
|
try {
|
|
297
|
-
await
|
|
397
|
+
await clearAiProfileAvatar(avatarEditorProfile.id);
|
|
298
398
|
}
|
|
299
399
|
catch (error) {
|
|
300
400
|
setAiAvatarError(String(error?.message || 'Failed to remove AI profile photo.'));
|
|
@@ -328,10 +428,10 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
328
428
|
{ label: 'Model', value: profile.model || '' },
|
|
329
429
|
].map((row) => ({ ...row, value: formatRowValue(row.value) })),
|
|
330
430
|
dates: [
|
|
431
|
+
{ label: 'Status', value: profile.archivedAt ? 'Retired' : 'Active' },
|
|
331
432
|
{ label: 'Recruited', value: formatTimestamp(profile.createdAt) },
|
|
332
433
|
{ label: 'Last updated', value: formatTimestamp(profile.updatedAt) },
|
|
333
434
|
{ label: 'Last active', value: formatTimestamp(profile.lastActiveAt) },
|
|
334
|
-
{ label: 'Status', value: profile.archivedAt ? 'Retired' : 'Active' },
|
|
335
435
|
].map((row) => ({ ...row, value: formatRowValue(row.value) })),
|
|
336
436
|
};
|
|
337
437
|
}, [selectedGroup]);
|
|
@@ -345,7 +445,8 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
345
445
|
: displayedSeatUsage
|
|
346
446
|
? `${displayedSeatUsage.used}/${displayedSeatUsage.limit === null ? 'Unlimited' : displayedSeatUsage.limit}`
|
|
347
447
|
: null;
|
|
348
|
-
|
|
448
|
+
const showAgentTray = !!mcpSettingsNode;
|
|
449
|
+
return (_jsxs("section", { className: `${styles.agentsModuleRoot} ${showAgentTray ? styles.agentsModuleWithTray : ''} ${showAgentTray && agentTrayOpen ? styles.agentsModuleTrayOpen : ''}`.trim(), children: [showAgentTray && (_jsxs("aside", { className: `${styles.agentTrayPanel} ${agentTrayOpen ? styles.agentTrayPanelOpen : ''}`.trim(), "aria-label": "Agent MCP settings tray", "aria-hidden": !agentTrayOpen, children: [_jsxs("div", { className: styles.agentTrayHeader, children: [_jsxs("span", { className: styles.agentTrayTitle, children: [_jsx(Plug, { size: 14 }), "MCP Settings"] }), _jsx("button", { type: "button", className: "tf-control-icon", onClick: onCloseAgentTray, title: "Collapse agent tray", "aria-label": "Collapse agent tray", children: _jsx(ChevronLeft, { size: 16 }) })] }), _jsx("div", { className: `${styles.agentTrayContent} tf-scrollbar tf-scrollbar--track-transparent`, children: _jsx("div", { className: styles.agentTrayContentInner, children: mcpSettingsNode }) })] })), _jsx("div", { className: styles.agentsModuleContent, children: _jsx("div", { className: `${styles.settingGroup} ${styles.agentsModuleGroup}`, children: _jsxs("div", { children: [_jsx("h4", { className: styles.settingSubTitle, children: "Registered AI Profiles" }), _jsx("p", { className: `${styles.settingsHint} ${styles.marginBottom12}`, children: "AI agents register profiles when connecting via MCP. Merge duplicates created when a token was lost, or remove inactive profiles from the active roster to free seats while preserving history." }), cloudAgentSeatLabel && (_jsx("div", { className: styles.aiProfileSeatSummary, children: shouldPromptForCloudLogin
|
|
349
450
|
? cloudAgentSeatLabel
|
|
350
451
|
: `Registered Cloud Agents: ${cloudAgentSeatLabel}` })), aiProfilesLoading && (_jsxs("div", { className: styles.settingsHint, children: [_jsx(Loader2, { size: 13, className: styles.spinner }), " Loading profiles\u2026"] })), !aiProfilesLoading && aiProfiles.length === 0 && (_jsx("div", { className: styles.settingsHint, children: "No AI profiles registered yet." })), !aiProfilesLoading && groupedProfilesBySurfaceType.length > 0 && (_jsxs("div", { className: styles.aiProfilesExplorer, children: [_jsx("div", { className: `${styles.aiProfilesListPane} tf-scrollbar tf-scrollbar--track-transparent`, children: _jsx("div", { className: styles.aiProfilesList, children: groupedProfilesBySurfaceType.map((section) => (_jsxs("div", { className: styles.aiProfilesSection, children: [_jsx("div", { className: styles.aiProfilesSectionHeader, children: section.label }), section.groups.map((group) => {
|
|
351
452
|
const isSelected = selectedGroup?.groupId === group.groupId;
|
|
@@ -355,19 +456,21 @@ export function AgentsModule({ workspaceId, cloudAuthConfigured = false, authSes
|
|
|
355
456
|
return;
|
|
356
457
|
event.preventDefault();
|
|
357
458
|
setSelectedGroupId(group.groupId);
|
|
358
|
-
}, "aria-pressed": isSelected, children: [renderAiProfileSeatScopeIcon(primary.seatScope), _jsxs("div", { className: styles.aiProfileGroupHeader, children: [_jsx("span", { className: styles.aiProfileGroupAvatar, style: { color: primary.color }, children: primary.avatarUrl ? (_jsx("img", { src: primary
|
|
359
|
-
})] }, section.section))) }) }), selectedGroup && (_jsx("div", { className: styles.aiProfileDetailPane, children: _jsxs("div", { className: styles.aiProfileDetailCard, children: [renderAiProfileSeatScopeIcon(selectedGroup.primaryProfile.seatScope, { variant: 'detail' }), _jsxs("div", { className: styles.aiProfileDetailHero, children: [_jsx(EditableAvatarButton, { label: "Edit AI profile photo", imageUrl: selectedGroup.primaryProfile
|
|
459
|
+
}, "aria-pressed": isSelected, children: [renderAiProfileSeatScopeIcon(primary.seatScope), _jsxs("div", { className: styles.aiProfileGroupHeader, children: [_jsx("span", { className: styles.aiProfileGroupAvatar, style: { color: primary.color }, children: primary.avatarUrl ? (_jsx("img", { src: getAiProfileAvatarDisplayUrl(primary), alt: "" })) : (_jsx(Bot, { size: 22 })) }), _jsxs("span", { className: styles.aiProfileGroupIdentity, children: [_jsx("span", { className: styles.aiProfileName, children: primary.name }), _jsxs("span", { className: styles.aiProfileHandle, children: ["@", primary.username] }), _jsx("span", { className: styles.aiProfileRole, children: primary.role || 'Role not set' }), group.profiles.length > 1 && (_jsxs("span", { className: styles.aiProfileDuplicateBadge, children: [_jsx(AlertTriangle, { size: 11 }), " ", formatDuplicateCount(group.profiles.length)] }))] })] })] }, group.groupId));
|
|
460
|
+
})] }, section.section))) }) }), selectedGroup && (_jsx("div", { className: styles.aiProfileDetailPane, children: _jsxs("div", { className: styles.aiProfileDetailCard, children: [renderAiProfileSeatScopeIcon(selectedGroup.primaryProfile.seatScope, { variant: 'detail' }), _jsxs("div", { className: styles.aiProfileDetailHero, children: [_jsx(EditableAvatarButton, { label: "Edit AI profile photo", imageUrl: getAiProfileAvatarDisplayUrl(selectedGroup.primaryProfile), fallback: _jsx(Bot, { size: 38 }), accentColor: selectedGroup.primaryProfile.color, size: 176, width: 153, height: 207, radius: 6, editBadgeSize: 28, editIconSize: 14, className: styles.aiProfileDetailAvatar, onClick: () => {
|
|
360
461
|
setAiAvatarError(null);
|
|
361
462
|
setAvatarEditorProfileId(selectedGroup.primaryProfile.id);
|
|
362
|
-
} }), _jsxs("div", { className: styles.aiProfileDetailHeading, children: [_jsx("div", { className: styles.aiProfileDetailTitleRow, children: _jsx("h5", { className: styles.aiProfileDetailTitle, children: selectedGroup.primaryProfile.name }) }), _jsxs("div", { className: styles.aiProfileDetailMetaRow, children: [_jsxs("span", { className: styles.aiProfileDetailHandle, children: ["@", selectedGroup.primaryProfile.username] }), selectedGroup.profiles.length > 1 && (_jsxs("span", { className: styles.aiProfileDetailLinkedCount, children: [selectedGroup.profiles.length, " linked"] }))] }), _jsxs("div", { className: styles.aiProfileDetailRole, children: ["Role: ", selectedGroup.primaryProfile.role || 'Not set'] }), selectedGroup.primaryProfile.description && (_jsx("div", { className: styles.aiProfileDetailDescription, children: selectedGroup.primaryProfile.description }))
|
|
463
|
+
} }), _jsxs("div", { className: styles.aiProfileDetailHeading, children: [_jsx("div", { className: styles.aiProfileDetailTitleRow, children: _jsx("h5", { className: styles.aiProfileDetailTitle, children: selectedGroup.primaryProfile.name }) }), _jsxs("div", { className: styles.aiProfileDetailMetaRow, children: [_jsxs("span", { className: styles.aiProfileDetailHandle, children: ["@", selectedGroup.primaryProfile.username] }), selectedGroup.profiles.length > 1 && (_jsxs("span", { className: styles.aiProfileDetailLinkedCount, children: [selectedGroup.profiles.length, " linked"] }))] }), _jsxs("div", { className: styles.aiProfileDetailRole, children: ["Role: ", selectedGroup.primaryProfile.role || 'Not set'] }), selectedGroup.primaryProfile.description && (_jsx("div", { className: styles.aiProfileDetailDescription, children: selectedGroup.primaryProfile.description })), _jsxs("div", { className: styles.aiProfileSignatureColor, children: [_jsx("span", { children: "Signature color" }), _jsx("span", { className: styles.aiProfileSignatureSwatch, style: { backgroundColor: selectedGroup.primaryProfile.color }, "aria-hidden": "true" }), _jsx("span", { children: selectedGroup.primaryProfile.color })] })] })] }), _jsxs("div", { className: styles.aiProfileDetailDataList, children: [_jsx("div", { className: styles.aiProfileDetailDataGroup, children: selectedProfileDataGroups.attributes.map((row) => (_jsxs("div", { className: styles.aiProfileDetailDataRow, children: [_jsx("span", { className: styles.aiProfileDetailDataLabel, children: row.label }), row.label === 'Category' ? (_jsx("span", { className: styles.aiProfileDetailDataValue, children: _jsxs("select", { className: styles.aiProfileStatusSelect, "aria-label": "Agent category", value: selectedGroup.primaryProfile.surfaceType ?? '', disabled: aiProfileCategoryBusy, onChange: (event) => {
|
|
464
|
+
void handleCategoryChange(selectedGroup, event.target.value);
|
|
465
|
+
}, children: [_jsx("option", { value: "", children: "Unclassified" }), AI_PROFILE_SURFACE_TYPES.map((surfaceType) => (_jsx("option", { value: surfaceType, children: formatAiProfileSurfaceTypeLabel(surfaceType) }, surfaceType)))] }) })) : (_jsx("span", { className: styles.aiProfileDetailDataValue, children: row.value }))] }, row.label))) }), _jsx("div", { className: `${styles.aiProfileDetailDataGroup} ${styles.aiProfileDetailDateGroup}`, children: selectedProfileDataGroups.dates.map((row) => (_jsxs("div", { className: styles.aiProfileDetailDataRow, children: [_jsx("span", { className: styles.aiProfileDetailDataLabel, children: row.label }), row.label === 'Status' ? (_jsx("span", { className: styles.aiProfileDetailDataValue, children: _jsxs("select", { className: styles.aiProfileStatusSelect, "aria-label": "Agent roster status", value: selectedGroup.primaryProfile.archivedAt ? 'retired' : 'active', onChange: (event) => {
|
|
363
466
|
if (event.target.value === 'retire') {
|
|
364
467
|
void handleArchive(selectedGroup.primaryProfile);
|
|
365
468
|
}
|
|
366
|
-
}, children: [_jsx("option", { value: "active", children: "Active" }), selectedGroup.primaryProfile.archivedAt ? (_jsx("option", { value: "retired", children: "Retired" })) : (_jsx("option", { value: "retire", children: "Retire from roster" }))] }) })) : (_jsx("span", { className: styles.aiProfileDetailDataValue, children: row.value }))] }, row.label))) })] }), selectedGroupHasDuplicates ? (_jsxs("div", { className: styles.aiProfileInstanceSection, children: [_jsxs("div", { className: styles.aiProfileInstanceSectionHeader, children: [_jsx("span", { children: "Profile instances" }), _jsx("span", { className: styles.settingsHint, children: "Choose a keeper here if duplicates need to be merged." })] }), _jsx("div", { className: styles.aiProfileInstanceList, children: selectedGroup.profiles.map((profile) => (_jsxs("div", { className: styles.aiProfileInstanceCard, children: [_jsxs("div", { className: styles.aiProfileInstanceTopRow, children: [_jsxs("div", { children: [_jsxs("div", { className: styles.aiProfileInstanceName, children: ["@", profile.username] }), _jsx("div", { className: styles.aiProfileIdChip, children: profile.id })] }), aiProfileMergeState?.keepId === profile.id && (_jsx("span", { className: styles.aiProfileKeepBadge, children: "Keeping" }))] }), _jsxs("div", { className: styles.aiProfileInstanceMeta, children: [_jsxs("span", { children: ["Created ", formatTimestamp(profile.createdAt)] }), _jsxs("span", { children: ["Updated ", formatTimestamp(profile.updatedAt)] })] }), aiProfileMergeState?.keepId !== profile.id && (_jsx("button", { className: styles.secondaryHeaderBtn, title: "Keep this profile, merge others into it", onClick: () => {
|
|
469
|
+
}, children: [_jsx("option", { value: "active", children: "Active" }), selectedGroup.primaryProfile.archivedAt ? (_jsx("option", { value: "retired", children: "Retired" })) : (_jsx("option", { value: "retire", children: "Retire from roster" }))] }) })) : (_jsx("span", { className: styles.aiProfileDetailDataValue, children: row.value }))] }, row.label))) })] }), aiProfileCategoryError?.profileId === selectedGroup.primaryProfile.id && (_jsxs("div", { className: styles.aiProfileInlineError, role: "alert", children: [_jsx(AlertTriangle, { size: 12 }), _jsx("span", { children: aiProfileCategoryError.message })] })), selectedGroupHasDuplicates ? (_jsxs("div", { className: styles.aiProfileInstanceSection, children: [_jsxs("div", { className: styles.aiProfileInstanceSectionHeader, children: [_jsx("span", { children: "Profile instances" }), _jsx("span", { className: styles.settingsHint, children: "Choose a keeper here if duplicates need to be merged." })] }), _jsx("div", { className: styles.aiProfileInstanceList, children: selectedGroup.profiles.map((profile) => (_jsxs("div", { className: styles.aiProfileInstanceCard, children: [_jsxs("div", { className: styles.aiProfileInstanceTopRow, children: [_jsxs("div", { children: [_jsxs("div", { className: styles.aiProfileInstanceName, children: ["@", profile.username] }), _jsx("div", { className: styles.aiProfileIdChip, children: profile.id })] }), aiProfileMergeState?.keepId === profile.id && (_jsx("span", { className: styles.aiProfileKeepBadge, children: "Keeping" }))] }), _jsxs("div", { className: styles.aiProfileInstanceMeta, children: [_jsxs("span", { children: ["Created ", formatTimestamp(profile.createdAt)] }), _jsxs("span", { children: ["Updated ", formatTimestamp(profile.updatedAt)] })] }), aiProfileMergeState?.keepId !== profile.id && (_jsx("button", { className: styles.secondaryHeaderBtn, title: "Keep this profile, merge others into it", onClick: () => {
|
|
367
470
|
const otherId = selectedGroup.profiles.find((candidate) => candidate.id !== profile.id)?.id;
|
|
368
471
|
if (otherId)
|
|
369
472
|
setAiProfileMergeState({ keepId: profile.id, mergeId: otherId });
|
|
370
|
-
}, children: "Keep this" })), aiProfileArchiveError?.profileId === profile.id && (_jsxs("div", { className: styles.aiProfileInlineError, role: "alert", children: [_jsx(AlertTriangle, { size: 12 }), _jsx("span", { children: aiProfileArchiveError.message })] })), _jsx("button", { className: styles.aiProfileDangerTextButton, title: "Remove this profile from the active roster", onClick: () => { void handleArchive(profile); }, children: "Remove from roster" })] }, profile.id))) }), aiProfileMergeState && selectedGroup.profiles.some((profile) => profile.id === aiProfileMergeState.keepId) && (_jsxs("div", { className: styles.aiProfileMergeActions, children: [_jsx("button", { className: styles.dangerBtn, onClick: () => { void handleMerge(aiProfileMergeState.keepId, aiProfileMergeState.mergeId); }, children: "Merge duplicates" }), _jsx("button", { className: styles.secondaryHeaderBtn, onClick: () => setAiProfileMergeState(null), children: "Cancel" })] }))] })) : (_jsxs("div", { className: styles.aiProfileIdFooter, children: [_jsx("div", { className: styles.aiProfileIdFooterValue, children: selectedGroup.primaryProfile.id }), aiProfileArchiveError?.profileId === selectedGroup.primaryProfile.id && (_jsxs("div", { className: styles.aiProfileInlineError, role: "alert", children: [_jsx(AlertTriangle, { size: 12 }), _jsx("span", { children: aiProfileArchiveError.message })] }))] }))] }) }))] })), aiProfileMergeStatus && (_jsxs("div", { className: `${aiProfileMergeStatus.type === 'success' ? styles.successMessage : styles.errorMessage} ${styles.marginTop12}`, children: [aiProfileMergeStatus.type === 'success' ? _jsx(Check, { size: 14 }) : _jsx(AlertTriangle, { size: 14 }), aiProfileMergeStatus.message] }))] }) }) }), _jsx(AvatarImageManagerModal, { isOpen: !!avatarEditorProfile, theme: "dark", title: "Edit AI Profile Photo", currentImageUrl: avatarEditorProfile
|
|
473
|
+
}, children: "Keep this" })), aiProfileArchiveError?.profileId === profile.id && (_jsxs("div", { className: styles.aiProfileInlineError, role: "alert", children: [_jsx(AlertTriangle, { size: 12 }), _jsx("span", { children: aiProfileArchiveError.message })] })), _jsx("button", { className: styles.aiProfileDangerTextButton, title: "Remove this profile from the active roster", onClick: () => { void handleArchive(profile); }, children: "Remove from roster" })] }, profile.id))) }), aiProfileMergeState && selectedGroup.profiles.some((profile) => profile.id === aiProfileMergeState.keepId) && (_jsxs("div", { className: styles.aiProfileMergeActions, children: [_jsx("button", { className: styles.dangerBtn, onClick: () => { void handleMerge(aiProfileMergeState.keepId, aiProfileMergeState.mergeId); }, children: "Merge duplicates" }), _jsx("button", { className: styles.secondaryHeaderBtn, onClick: () => setAiProfileMergeState(null), children: "Cancel" })] }))] })) : (_jsxs("div", { className: styles.aiProfileIdFooter, children: [_jsx("div", { className: styles.aiProfileIdFooterValue, children: selectedGroup.primaryProfile.id }), aiProfileArchiveError?.profileId === selectedGroup.primaryProfile.id && (_jsxs("div", { className: styles.aiProfileInlineError, role: "alert", children: [_jsx(AlertTriangle, { size: 12 }), _jsx("span", { children: aiProfileArchiveError.message })] }))] }))] }) }))] })), aiProfileMergeStatus && (_jsxs("div", { className: `${aiProfileMergeStatus.type === 'success' ? styles.successMessage : styles.errorMessage} ${styles.marginTop12}`, children: [aiProfileMergeStatus.type === 'success' ? _jsx(Check, { size: 14 }) : _jsx(AlertTriangle, { size: 14 }), aiProfileMergeStatus.message] }))] }) }) }), _jsx(AvatarImageManagerModal, { isOpen: !!avatarEditorProfile, theme: "dark", title: "Edit AI Profile Photo", currentImageUrl: getAiProfileAvatarDisplayUrl(avatarEditorProfile), editorImageUrl: getAiProfileAvatarSourceUrl(avatarEditorProfile), fallbackInitial: (avatarEditorProfile?.name || 'AI').charAt(0).toUpperCase(), accept: "image/png,image/jpeg,image/webp,image/gif", busy: aiAvatarBusy, hasPendingImage: false, canRemove: !!avatarEditorProfile?.avatarUrl, error: aiAvatarError, notice: null, onClose: () => {
|
|
371
474
|
if (aiAvatarBusy)
|
|
372
475
|
return;
|
|
373
476
|
setAvatarEditorProfileId(null);
|