Replace Mission Control iframe on the Heute pillar with a native React brief that surfaces actionable runs, unread replies, and today’s threads. Mission Control remains available via a small footer link for power users. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
122 lines
3.4 KiB
TypeScript
122 lines
3.4 KiB
TypeScript
import type { Bot, Group, RunActivityRow } from "@rakazo/contracts";
|
|
|
|
export type JetztRunItem = {
|
|
kind: "run";
|
|
run: RunActivityRow;
|
|
};
|
|
|
|
export type JetztUnreadItem = {
|
|
kind: "unread";
|
|
chatKind: "bot" | "group";
|
|
chat: Bot | Group;
|
|
};
|
|
|
|
export type JetztItem = JetztRunItem | JetztUnreadItem;
|
|
|
|
export type WeiterItem = {
|
|
chatKind: "bot" | "group";
|
|
chat: Bot | Group;
|
|
};
|
|
|
|
export type HeuteBriefData = {
|
|
jetzt: JetztItem[];
|
|
weiter: WeiterItem[];
|
|
actionCount: number;
|
|
};
|
|
|
|
export function isToday(iso: string, now = new Date()): boolean {
|
|
const date = new Date(iso);
|
|
if (Number.isNaN(date.getTime())) return false;
|
|
return (
|
|
date.getDate() === now.getDate() &&
|
|
date.getMonth() === now.getMonth() &&
|
|
date.getFullYear() === now.getFullYear()
|
|
);
|
|
}
|
|
|
|
export function cleanBriefPreview(raw?: string | null): string {
|
|
if (!raw) return "";
|
|
let text = raw;
|
|
text = text.replace(/^#{1,6}\s+/gm, "");
|
|
text = text.replace(/\[(?:to|RECOVERY PROTOCOL|PO)[^\]]*\]/gi, "");
|
|
text = text.replace(/[*_`#]/g, "");
|
|
text = text.replace(/\\/g, "");
|
|
return text.replace(/\s+/g, " ").trim();
|
|
}
|
|
|
|
function runTargetKey(run: RunActivityRow): string {
|
|
return run.groupId ? `group:${run.groupId}` : `bot:${run.botId}`;
|
|
}
|
|
|
|
function chatTargetKey(chatKind: "bot" | "group", chatId: string): string {
|
|
return chatKind === "group" ? `group:${chatId}` : `bot:${chatId}`;
|
|
}
|
|
|
|
function isActionableRun(run: RunActivityRow): boolean {
|
|
return run.status === "waiting_input" || run.status === "waiting_takeover";
|
|
}
|
|
|
|
function isFailedRoutine(run: RunActivityRow): boolean {
|
|
return run.trigger === "routine" && run.status === "failed";
|
|
}
|
|
|
|
export function buildHeuteBrief(params: {
|
|
bots: Bot[];
|
|
groups: Group[];
|
|
activeRuns: RunActivityRow[];
|
|
recentRuns: RunActivityRow[];
|
|
now?: Date;
|
|
}): HeuteBriefData {
|
|
const { bots, groups, activeRuns, recentRuns, now = new Date() } = params;
|
|
const jetzt: JetztItem[] = [];
|
|
const coveredTargets = new Set<string>();
|
|
|
|
const actionableRuns = [
|
|
...activeRuns.filter(isActionableRun),
|
|
...recentRuns.filter(isFailedRoutine),
|
|
];
|
|
const seenRunIds = new Set<string>();
|
|
for (const run of actionableRuns) {
|
|
if (seenRunIds.has(run.runId)) continue;
|
|
seenRunIds.add(run.runId);
|
|
jetzt.push({ kind: "run", run });
|
|
coveredTargets.add(runTargetKey(run));
|
|
}
|
|
|
|
const unreadChats: JetztUnreadItem[] = [
|
|
...bots.filter((bot) => bot.unread).map((bot) => ({ kind: "unread" as const, chatKind: "bot" as const, chat: bot })),
|
|
...groups.filter((group) => group.unread).map((group) => ({
|
|
kind: "unread" as const,
|
|
chatKind: "group" as const,
|
|
chat: group,
|
|
})),
|
|
];
|
|
for (const item of unreadChats) {
|
|
const key = chatTargetKey(item.chatKind, item.chat.id);
|
|
if (coveredTargets.has(key)) continue;
|
|
jetzt.push(item);
|
|
coveredTargets.add(key);
|
|
}
|
|
|
|
const weiterCandidates: WeiterItem[] = [
|
|
...bots
|
|
.filter((bot) => isToday(bot.updatedAt, now))
|
|
.map((bot) => ({ chatKind: "bot" as const, chat: bot })),
|
|
...groups
|
|
.filter((group) => isToday(group.updatedAt, now))
|
|
.map((group) => ({ chatKind: "group" as const, chat: group })),
|
|
];
|
|
weiterCandidates.sort(
|
|
(a, b) => new Date(b.chat.updatedAt).getTime() - new Date(a.chat.updatedAt).getTime(),
|
|
);
|
|
const weiter = weiterCandidates
|
|
.filter((item) => !coveredTargets.has(chatTargetKey(item.chatKind, item.chat.id)))
|
|
.slice(0, 5);
|
|
|
|
return {
|
|
jetzt,
|
|
weiter,
|
|
actionCount: jetzt.length,
|
|
};
|
|
}
|