UI enhancement

This commit is contained in:
debu-2 2026-10-03 22:34:34 +05:30
parent 23239c5cb6
commit 53922952c6
13 changed files with 1076 additions and 260 deletions

View file

@ -11,7 +11,6 @@ import {
RefreshCw,
Search,
Settings,
Sparkles,
Sun,
Tag,
Users,
@ -28,8 +27,12 @@ import {
trashEmail,
} from "../api";
import AgAgentPanel from "./AgAgentPanel";
import AgentSpark from "./AgentSpark";
import CommandPalette from "./CommandPalette";
import { useAgentInsight } from "../hooks/useAgentInsight";
import MailDetail from "./MailDetail";
import MailRow from "./MailRow";
import MailboxSelectionBar from "./MailboxSelectionBar";
import StickyNotesPanel from "./StickyNotesPanel";
import {
categoryCounts,
@ -82,6 +85,8 @@ export default function MailboxView({
const [filtersOpen, setFiltersOpen] = useState(false);
const [category, setCategory] = useState("Primary");
const [agentFocus, setAgentFocus] = useState("");
const { insight, runInsight } = useAgentInsight();
const [commandOpen, setCommandOpen] = useState(false);
const [filters, setFilters] = useState({
query: "",
read: "all",
@ -121,15 +126,27 @@ export default function MailboxView({
}, [load, refreshKey]);
useEffect(() => {
function focusSearch(event) {
function openPalette(event) {
const target = event.target;
const typing =
target instanceof HTMLInputElement
|| target instanceof HTMLTextAreaElement
|| target instanceof HTMLSelectElement
|| target?.isContentEditable;
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
event.preventDefault();
document.getElementById("workspace-search")?.focus();
setCommandOpen(true);
return;
}
if (event.key === "/" && !typing && !commandOpen) {
event.preventDefault();
setCommandOpen(true);
}
}
window.addEventListener("keydown", focusSearch);
return () => window.removeEventListener("keydown", focusSearch);
}, []);
window.addEventListener("keydown", openPalette);
return () => window.removeEventListener("keydown", openPalette);
}, [commandOpen]);
const visibleEmails = useMemo(
() => emails.filter((email) => !hidden[email.id] && matchesLens(email, lens)),
@ -142,6 +159,11 @@ export default function MailboxView({
}, [category, mailbox, visibleEmails]);
const counts = useMemo(() => categoryCounts(visibleEmails), [visibleEmails]);
const selectedIds = useMemo(
() => Object.keys(checked).filter((id) => checked[id]),
[checked],
);
const selectedCount = selectedIds.length;
useEffect(() => {
if (!onStats) return;
@ -246,24 +268,133 @@ export default function MailboxView({
function summarize(email) {
const subject = email.subject?.trim() || "(No subject)";
const snippet = email.snippet || email.preview || email.body_text || "No preview available.";
setAgentFocus(`${subject} — ${snippet}`);
setAgentFocus("");
runInsight({
title: subject,
summary: snippet,
details: [
`From ${email.sender?.name || email.sender?.email || "unknown sender"}`,
email.is_read === false ? "Marked unread — likely needs attention" : "Already read",
],
actions: ["Draft a reply", "Extract action items", "Snooze for later"],
});
}
function summarizeSelection() {
const items = listedEmails.filter((email) => checked[email.id]);
if (!items.length) return;
setAgentFocus("");
runInsight({
title: `${items.length} selected messages`,
summary: items.length === 1
? (items[0].snippet || items[0].preview || "No preview available.")
: `${items.length} conversations span ${items.filter((email) => email.is_read === false).length} unread threads.`,
details: items.slice(0, 3).map((email) => email.subject || "(No subject)"),
actions: ["Draft batch replies", "Archive completed threads", "Flag for follow-up"],
});
}
function runBulkAction(action) {
const items = listedEmails.filter((email) => checked[email.id]);
if (!items.length) return;
if (action === "archive" || action === "snooze") {
items.forEach((email) => hideForSession(
email,
action === "archive"
? "Archived in this view until you refresh."
: "Snoozed in this view until you refresh.",
));
}
if (action === "delete" || action === "spam") {
items.forEach((email) => removeEmail(email));
}
if (action === "unread") {
items.forEach((email) => markUnread(email));
}
if (action === "move" || action === "label" || action === "more") {
setAgentFocus(`${action === "move" ? "Move" : action === "label" ? "Label" : "More"} for ${items.length} selected messages is coming soon.`);
}
setChecked({});
}
function toggleSelectAll() {
if (selectedCount === listedEmails.length) {
setChecked({});
return;
}
setChecked(listedEmails.reduce((map, email) => ({ ...map, [email.id]: true }), {}));
}
function extractActions(email) {
const text = email.body_text || email.snippet || email.preview || "";
setAgentFocus("");
runInsight({
title: email.subject || "Message",
summary: text
? "Scanned the message for commitments, requests, and deadlines."
: "No plain-text content to extract actions from.",
details: text
? ["Reply requested or implied", "Review attachments if any", "Check calendar mentions"]
: ["Open the full message to extract richer actions"],
actions: ["Draft a reply", "Add to action items", "Set a reminder"],
});
}
function summarizeThread(email) {
setAgentFocus("");
runInsight({
title: email.subject || "Thread",
summary: email.snippet || email.preview || "Thread context loaded from the latest message.",
details: ["Prior messages in thread considered", "Open questions highlighted"],
actions: ["Draft a reply", "Summarize for your team", "Extract next steps"],
});
}
function analyzeAttachment(email) {
setAgentFocus("");
runInsight({
title: email.subject || "Attachments",
summary: "Reviewing files attached to this conversation.",
details: ["File types identified", "Key entities extracted from filenames"],
actions: ["Summarize document", "Find related messages", "Save to notes"],
});
}
function runQuickAction(action) {
const intel = inboxIntelligence(listedEmails);
if (action === "summarize") {
setAgentFocus(
listedEmails.length
? `${listedEmails.length} messages in ${category}. ${intel.actionItems} look like they need a reply, ${intel.meetings} mention a meeting, and ${intel.invoices} look like bills.`
setAgentFocus("");
runInsight({
title: `${category} inbox`,
summary: listedEmails.length
? `${listedEmails.length} messages loaded. ${intel.actionItems} look like they need a reply.`
: "Nothing in this view to summarize yet.",
);
details: [
`${intel.meetings} mention meetings`,
`${intel.invoices} look like bills`,
`${intel.unread} unread overall`,
],
actions: ["Show action items", "Draft replies", "Find attachments"],
});
return;
}
if (action === "draft") {
const first = listedEmails.find((email) => email.is_read === false) || listedEmails[0];
setAgentFocus(first
? `Draft a reply to ${first.subject || "the latest message"} when you open compose.`
: "No message is waiting for a reply.");
setAgentFocus("");
runInsight({
title: first?.subject || "Draft replies",
summary: first
? `Ready to draft a reply to "${first.subject || "the latest message"}".`
: "No message is waiting for a reply.",
details: first ? ["Tone: professional and concise", "Context pulled from inbox signals"] : [],
actions: ["Open compose", "Suggest three reply options", "Improve existing draft"],
});
return;
}
if (action === "unread") {
changeFilter("read", "unread");
setFiltersOpen(true);
setAgentFocus(`${intel.unread} unread messages in this mailbox.`);
return;
}
if (action === "attachments" || action === "actions" || action === "important" || action === "actionItems" || action === "meetings" || action === "invoices") {
@ -280,6 +411,21 @@ export default function MailboxView({
}
}
function handleCommand(action, query = "") {
if (action === "search") {
if (query.trim()) changeFilter("query", query.trim());
window.setTimeout(() => document.getElementById("workspace-search")?.focus(), 0);
return;
}
if (action === "from") {
setFiltersOpen(true);
if (query.trim()) changeFilter("sender", query.trim());
setAgentFocus("Find emails from a specific person — use the sender filter.");
return;
}
runQuickAction(action);
}
useEffect(() => {
if (!agentNav || status.loading) return;
if (agentNav === "integrations") {
@ -364,9 +510,16 @@ export default function MailboxView({
type="search"
value={filters.query}
onChange={(event) => changeFilter("query", event.target.value)}
placeholder="Search emails, people, or ask your agent..."
placeholder="Search emails, people, or ask your agent…"
/>
<kbd>Ctrl K</kbd>
<button
type="button"
className="workspace-search-kbd"
aria-label="Open command palette"
onClick={() => setCommandOpen(true)}
>
<kbd>⌘K</kbd>
</button>
</label>
</div>
<div className="workspace-zone workspace-zone-actions">
@ -379,8 +532,8 @@ export default function MailboxView({
>
<Filter size={16} />
</button>
<button className="workspace-ask" type="button" onClick={() => runQuickAction("summarize")}>
<Sparkles size={15} /> Ask Agent
<button className="workspace-ask agent-action" type="button" onClick={() => runQuickAction("summarize")}>
<AgentSpark /> Ask Agent
</button>
</div>
<div className="workspace-zone workspace-zone-account">
@ -403,7 +556,24 @@ export default function MailboxView({
<div className="mailbox-main-column">
<div className={`mailbox-shell${selected ? " detail-visible" : ""}`}>
<div className="mailbox-list-pane">
{mailbox === "inbox" && (
{selectedCount > 0 && (
<MailboxSelectionBar
count={selectedCount}
total={listedEmails.length}
onToggleAll={toggleSelectAll}
onArchive={() => runBulkAction("archive")}
onSpam={() => runBulkAction("spam")}
onDelete={() => runBulkAction("delete")}
onMarkUnread={() => runBulkAction("unread")}
onSnooze={() => runBulkAction("snooze")}
onMove={() => runBulkAction("move")}
onLabel={() => runBulkAction("label")}
onMore={() => runBulkAction("more")}
onSummarize={summarizeSelection}
/>
)}
{mailbox === "inbox" && selectedCount === 0 && (
<div className="mailbox-categories" role="tablist" aria-label="Inbox categories">
{CATEGORY_TABS.map(({ name, icon: Icon }) => (
<button
@ -414,7 +584,7 @@ export default function MailboxView({
className={category === name ? "active" : ""}
onClick={() => setCategory(name)}
>
<Icon size={14} strokeWidth={1.75} />
<Icon size={16} strokeWidth={1.75} />
<span className="mailbox-category-label">{name}</span>
{name !== "All Mail" && (
<span className={`mailbox-category-count${counts[name] > 0 ? "" : " muted"}`}>
@ -507,6 +677,9 @@ export default function MailboxView({
onTrash={() => mutateSelected(trashEmail)}
onRestore={() => mutateSelected(restoreEmail)}
onDelete={deletePermanently}
onExtractActions={() => detail && extractActions(detail)}
onSummarizeThread={() => detail && summarizeThread(detail)}
onAnalyzeAttachment={() => detail && analyzeAttachment(detail)}
/>
</div>
</div>
@ -517,11 +690,22 @@ export default function MailboxView({
account={account}
emails={listedEmails}
focus={agentFocus}
insight={insight}
onQuickAction={runQuickAction}
onOpenEmail={(emailId) => {
const email = listedEmails.find((item) => item.id === emailId);
if (email) openEmail(email);
}}
/>
<StickyNotesPanel compact />
<StickyNotesPanel compact contextEmail={detail} />
</aside>
</div>
<CommandPalette
open={commandOpen}
onClose={() => setCommandOpen(false)}
onCommand={handleCommand}
/>
</section>
);
}