// frontend/components/mail/Triage.tsx — 아리 비서 다이제스트(아리 정리 보기) // 새 메일 중 '챙길 거리'를 발신자 카드로 정리: 요약 + 할 일/일정/답장 액션을 바로 적용. "use client"; import { Icon } from "@/components/Icon"; import type { AiAnalysis, AiReply, EmailRow, ExtractKind, MailAccount, Person, } from "@/lib/types"; const PRIO_RANK: Record = { 높음: 0, 보통: 1, 낮음: 2 }; // 메일 한 통에 달린 액션 수(할 일 + 일정 + 답장 1). function actionCount(ai: AiAnalysis): number { return ai.tasks.length + ai.events.length + (ai.replies.length ? 1 : 0); } // 카드 상태 배지 — 우선순위·액션 종류에서 비서 톤으로 산출. function statusOf(ai: AiAnalysis): { text: string; tone: string } { if (ai.priority === "높음") return { text: "중요 · 액션 필요", tone: "coral" }; if (ai.events.length) return { text: "일정 · 준비 필요", tone: "violet" }; if (ai.tasks.length) return { text: "할 일 · 확인 필요", tone: "blue" }; if (ai.replies.length) return { text: "답장 추천", tone: "green" }; return { text: "검토 필요", tone: "muted" }; } function ActionRow({ icon, kind, title, meta, done, onAdd, }: { icon: "check" | "cal"; kind: "task" | "event"; title: string; meta: string; done: boolean; onAdd: () => void; }) { return (
{title} {meta && {meta}}
); } function SecretaryCard({ m, name, account, accColor, added, onOpen, onAdd, onAddAll, onReply, onDismiss, }: { m: EmailRow; name: string; account?: MailAccount; accColor: string; added: Record; onOpen: (id: string) => void; onAdd: (mailId: string, key: string, kind: ExtractKind, index: number) => void; onAddAll: (m: EmailRow) => void; onReply: (m: EmailRow, idx: number, r: AiReply) => void; onDismiss: (id: string) => void; }) { const ai = m.ai; if (!ai) return null; const status = statusOf(ai); const n = actionCount(ai); const reply = ai.replies[0]; // 아바타는 받은편지함 행과 동일하게 '어느 계정'(발신자 아님) — 계정 색 + 계정 이니셜. const accName = account?.name ?? m.account; const avaColor = accColor ?? (account?.tone ? `var(--${account.tone})` : "var(--muted)"); return (
{accName.slice(0, 1).toUpperCase()}
{name} {status.text}

onOpen(m.id)}> {ai.summary || m.subject}

{ai.summary && m.subject !== ai.summary &&

{m.subject}

} {n > 0 && (
{ai.tasks.map((t, i) => ( onAdd(m.id, "t:" + m.id + ":" + i, "task", i)} /> ))} {ai.events.map((e, i) => ( onAdd(m.id, "e:" + m.id + ":" + i, "event", i)} /> ))} {reply && ( )}
)} {n > 1 && ( )}
); } export function Triage({ emails, people, accounts, accColor, added, newTotal, onOpenLog, onOpen, onAdd, onAddAll, onReply, onDismiss, }: { emails: EmailRow[]; people: Record; accounts: Record; accColor: (id: string) => string; added: Record; newTotal: number; onOpenLog: () => void; onOpen: (id: string) => void; onAdd: (mailId: string, key: string, kind: ExtractKind, index: number) => void; onAddAll: (m: EmailRow) => void; onReply: (m: EmailRow, idx: number, r: AiReply) => void; onDismiss: (id: string) => void; }) { const nameOf = (m: EmailRow) => people[m.from]?.name ?? m.from_name ?? m.from; // 새 메일 = 기준 시각 이후 도착(서버가 smart 보기로 이미 필터). 읽음 여부와 무관 — // 사용자가 폰/다른 앱에서 먼저 읽어도 아리가 챙길 거리를 정리한다. const fresh = emails.filter((m) => m.ai); // 챙길 거리: 할 일/일정/답장이 있거나 중요(높음)인 새 메일. const cards = fresh .filter((m) => m.ai && (actionCount(m.ai) > 0 || m.ai.priority === "높음")) .sort((a, b) => { const pa = PRIO_RANK[a.ai!.priority] ?? 1; const pb = PRIO_RANK[b.ai!.priority] ?? 1; if (pa !== pb) return pa - pb; return actionCount(b.ai!) - actionCount(a.ai!); }); const tot = cards.reduce( (acc, m) => { acc.tasks += m.ai!.tasks.length; acc.events += m.ai!.events.length; acc.replies += m.ai!.replies.length ? 1 : 0; return acc; }, { tasks: 0, events: 0, replies: 0 }, ); // 아직 아리가 정리하지 않은(분석 전) 새 메일 수. const pending = emails.filter((m) => !m.ai).length; const hasStats = tot.tasks + tot.events + tot.replies > 0; // 아리가 백그라운드로 자동 정리 중인 상태(수동 버튼 없음 — auto-sync 루프가 알아서 분석). const working = pending > 0; // 화면 상태 → 제목 한 줄 + 설명 한 줄. 같은 말을 두 번 하지 않도록 히어로 한 곳에서만 보여준다. let title: string; let sub: string; if (cards.length > 0) { title = `챙길 메일 ${cards.length}건을 추렸어요`; sub = `새 메일 ${fresh.length}통에서 챙길 거리만 골랐어요.`; } else if (working) { title = `새 메일 ${pending}통, 아리가 정리하는 중…`; sub = "잠시 후 챙길 거리만 카드로 정리해 둘게요."; } else if (newTotal > 0) { title = `오늘 온 새 메일 ${newTotal}통, 챙길 건 없었어요`; sub = "챙길 게 생기면 할 일·일정·답장 카드로 여기 정리해 둘게요."; } else { title = "지금은 챙길 새 메일이 없어요"; sub = "새 메일이 오면 아리가 할 일·일정·답장으로 정리해 둘게요."; } return (

{title}

{sub}

{hasStats && (
{tot.tasks > 0 && ( {tot.tasks} 할 일 )} {tot.events > 0 && ( {tot.events} 일정 )} {tot.replies > 0 && ( {tot.replies} 답장 )}
)} {/* 오늘 새 메일이 어떻게 분류/분석됐는지 상세는 다이얼로그로. */}
{cards.length > 0 && (
{cards.map((m) => ( ))}
)}
); }