agent-ecosystem/src/renderer/utils/crossTeamPendingReplies.ts
iliya 19f2fa76d0 feat: enhance cross-team functionality and UI components
- Added isOnline property to CrossTeamTarget and updated CrossTeamService to sort targets based on online status.
- Enhanced MessageComposer to fetch and display online status of teams, improving user experience in cross-team messaging.
- Updated various UI components to reflect changes in team online status, including visual indicators in the MessagesPanel and MessageComposer.
- Improved error handling and validation messages in CreateTeamDialog and other forms for better user feedback.
- Refactored CSS for field-level validation to enhance visual consistency across forms.
- Updated utility functions to support new online status features in team management.
2026-03-12 16:33:52 +02:00

92 lines
3.2 KiB
TypeScript

import type { InboxMessage } from '@shared/types';
export interface PendingCrossTeamReply {
teamName: string;
sentAtMs: number;
conversationId?: string;
}
export const CROSS_TEAM_PENDING_REPLY_TTL_MS = 10_000;
function parseQualifiedTeamName(value: string | undefined): string | null {
if (typeof value !== 'string') return null;
const trimmed = value.trim();
if (!trimmed) return null;
const dot = trimmed.indexOf('.');
if (dot <= 0) return null;
return trimmed.slice(0, dot);
}
export function computePendingCrossTeamReplies(
messages: InboxMessage[] | null | undefined,
nowMs = Date.now()
): PendingCrossTeamReply[] {
if (!messages || messages.length === 0) return [];
const latestSentByTeam = new Map<string, number>();
const latestInboundByTeam = new Map<string, number>();
const latestSentByConversation = new Map<
string,
{ teamName: string; sentAtMs: number; conversationId: string }
>();
const latestInboundByConversation = new Map<string, number>();
for (const message of messages) {
const timestampMs = Date.parse(message.timestamp);
if (!Number.isFinite(timestampMs)) continue;
if (message.source === 'cross_team_sent') {
const teamName = parseQualifiedTeamName(message.to);
if (!teamName) continue;
if (message.conversationId) {
const existing = latestSentByConversation.get(message.conversationId);
if (!existing || timestampMs > existing.sentAtMs) {
latestSentByConversation.set(message.conversationId, {
teamName,
sentAtMs: timestampMs,
conversationId: message.conversationId,
});
}
} else {
latestSentByTeam.set(teamName, Math.max(latestSentByTeam.get(teamName) ?? 0, timestampMs));
}
continue;
}
if (message.source === 'cross_team') {
const teamName = parseQualifiedTeamName(message.from);
if (!teamName) continue;
if (message.conversationId) {
latestInboundByConversation.set(
message.conversationId,
Math.max(latestInboundByConversation.get(message.conversationId) ?? 0, timestampMs)
);
} else {
latestInboundByTeam.set(
teamName,
Math.max(latestInboundByTeam.get(teamName) ?? 0, timestampMs)
);
}
}
}
const isWithinPendingWindow = (sentAtMs: number): boolean =>
sentAtMs >= nowMs || nowMs - sentAtMs <= CROSS_TEAM_PENDING_REPLY_TTL_MS;
const exactPending = Array.from(latestSentByConversation.values()).filter(
({ conversationId, sentAtMs }) =>
sentAtMs > (latestInboundByConversation.get(conversationId) ?? 0) &&
isWithinPendingWindow(sentAtMs)
);
const teamsCoveredExactly = new Set(exactPending.map((entry) => entry.teamName));
const legacyPending = Array.from(latestSentByTeam.entries())
.filter(([teamName]) => !teamsCoveredExactly.has(teamName))
.filter(
([teamName, sentAtMs]) =>
sentAtMs > (latestInboundByTeam.get(teamName) ?? 0) && isWithinPendingWindow(sentAtMs)
)
.map(([teamName, sentAtMs]) => ({ teamName, sentAtMs }))
.sort((a, b) => b.sentAtMs - a.sentAtMs);
return [...exactPending, ...legacyPending].sort((a, b) => b.sentAtMs - a.sentAtMs);
}