86 lines
2.7 KiB
TypeScript
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;
|
|
}
|