learning_ai_common_plat/dashboards/admin-web/src/lib/timeline-view.ts

86 lines
2.7 KiB
TypeScript

export type TimelineItem = {
itemId: string;
occurredAt: string;
eventName: string;
productId: string;
title: string;
summary?: string | null;
artifactRefs: string[];
relatedEventIds: string[];
actorType: 'user' | 'agent' | 'system' | 'device';
visibility: 'private' | 'org' | 'shared' | 'local-only';
correlationId?: string | null;
};
export type TimelineGroup = {
groupId: string;
correlationId: string | null;
items: TimelineItem[];
productIds: string[];
latestOccurredAt: string;
};
export const TIMELINE_PRODUCT_OPTIONS = [
{ value: 'current', label: 'Current Admin Product' },
{ value: 'lysnrai', label: 'LysnrAI' },
{ value: 'notelett', label: 'NoteLett' },
{ value: 'mindlyst', label: 'MindLyst' },
{ value: 'flowmonk', label: 'FlowMonk' },
{ value: 'chronomind', label: 'ChronoMind' },
{ value: 'efforise', label: 'EffoRise' },
{ value: 'actiontrail', label: 'ActionTrail' },
{ value: 'cowork', label: 'Cowork' },
] as const;
function compareDescendingIso(a: string, b: string) {
return new Date(b).getTime() - new Date(a).getTime();
}
export function getTimelineStats(items: TimelineItem[]) {
return {
uniqueProducts: new Set(items.map(item => item.productId)).size,
uniqueCorrelations: new Set(
items
.map(item => item.correlationId)
.filter((value): value is string => typeof value === 'string' && value.length > 0)
).size,
withArtifacts: items.filter(item => item.artifactRefs.length > 0).length,
groupedChains: buildTimelineGroups(items).filter(group => group.items.length > 1).length,
};
}
export function buildTimelineGroups(items: TimelineItem[]): TimelineGroup[] {
const groups = new Map<string, TimelineItem[]>();
for (const item of items) {
const groupKey = item.correlationId?.trim()
? `corr:${item.correlationId}`
: `item:${item.itemId}`;
const bucket = groups.get(groupKey);
if (bucket) {
bucket.push(item);
} else {
groups.set(groupKey, [item]);
}
}
return [...groups.entries()]
.map(([groupId, groupItems]) => {
const itemsSorted = [...groupItems].sort((left, right) =>
compareDescendingIso(left.occurredAt, right.occurredAt)
);
return {
groupId,
correlationId: itemsSorted[0]?.correlationId?.trim() || null,
items: itemsSorted,
productIds: [...new Set(itemsSorted.map(item => item.productId))],
latestOccurredAt: itemsSorted[0]?.occurredAt ?? new Date(0).toISOString(),
};
})
.sort((left, right) => compareDescendingIso(left.latestOccurredAt, right.latestOccurredAt));
}
export function getProductLabel(productId: string) {
return TIMELINE_PRODUCT_OPTIONS.find(option => option.value === productId)?.label ?? productId;
}