update
This commit is contained in:
1 parent
b15f1c0e5e
commit
b56aaa1498
71 files changed
+866
-659
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { useMemo } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
ArrowRight,
|
||||
Briefcase,
|
||||
@@ -28,6 +29,7 @@ import { RecoveryBanner } from "./RecoveryBanner";
|
||||
import { BudgetAlertBanner } from "./BudgetAlertBanner";
|
||||
|
||||
export interface DashboardProps {
|
||||
companyName?: string;
|
||||
agents: AgentRecord[];
|
||||
tasks: TaskRecord[];
|
||||
runs: RunRecord[];
|
||||
@@ -42,7 +44,10 @@ export interface DashboardProps {
|
||||
|
||||
export function Dashboard(props: DashboardProps) {
|
||||
const t = useT();
|
||||
const { agents, tasks, runs, approvals, activity, costs, messages = [], onNavigate } = props;
|
||||
const { companyName, agents, tasks, runs, approvals, activity, costs, messages = [], onNavigate } = props;
|
||||
|
||||
const agentMap = useMemo(() => new Map(agents.map((a) => [a.id, a])), [agents]);
|
||||
const taskMap = useMemo(() => new Map(tasks.map((t) => [t.id, t])), [tasks]);
|
||||
|
||||
const activeAgents = useMemo(() => agents.filter((a) => a.status !== "terminated" && a.status !== "pending_approval"), [agents]);
|
||||
const activeTasks = useMemo(() => tasks.filter((t) => t.status === "in_progress" || t.status === "in_review"), [tasks]);
|
||||
@@ -59,15 +64,35 @@ export function Dashboard(props: DashboardProps) {
|
||||
return runs
|
||||
.filter((r) => r.status === "running" || r.status === "queued")
|
||||
.map((run) => {
|
||||
const agent = agents.find((a) => a.id === run.agentId);
|
||||
const task = tasks.find((t) => t.id === run.taskId);
|
||||
const agent = agentMap.get(run.agentId);
|
||||
const task = taskMap.get(run.taskId);
|
||||
return { run, agent, task };
|
||||
})
|
||||
.slice(0, 8);
|
||||
}, [runs, agents, tasks]);
|
||||
}, [runs, agentMap, taskMap]);
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-6">
|
||||
{/* Company status line */}
|
||||
{companyName ? (
|
||||
<div>
|
||||
<h2 className="text-[28px] font-bold tracking-[-0.02em] text-[color:var(--text)]">{companyName}</h2>
|
||||
<div className="mt-2 flex items-center gap-3 text-[13px] tracking-[0.005em] text-[color:var(--muted)]">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-[color:var(--success)]" />
|
||||
{activeAgents.length} {activeAgents.length === 1 ? "agent" : "agents"} active
|
||||
</span>
|
||||
<span className="h-3 w-px bg-[color:var(--line-strong)]" />
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-[color:var(--accent)]" />
|
||||
{activeTasks.length} {activeTasks.length === 1 ? "task" : "tasks"} in progress
|
||||
</span>
|
||||
<span className="h-3 w-px bg-[color:var(--line-strong)]" />
|
||||
<span>{tasks.filter((tk) => tk.status === "done").length} completed</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* Conditional banners */}
|
||||
<RecoveryBanner />
|
||||
<BudgetAlertBanner agents={agents} onNavigate={onNavigate} />
|
||||
@@ -109,15 +134,19 @@ export function Dashboard(props: DashboardProps) {
|
||||
{/* Needs Attention — moved to top, only shows when items exist */}
|
||||
{hasAttentionItems ? (
|
||||
<section>
|
||||
<div className="mb-3 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--warn)]">
|
||||
<div className="mb-3 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--warn)]">
|
||||
<span className="h-1.5 w-1.5 animate-pulse rounded-full bg-[color:var(--warn)]" />
|
||||
{t("dash.needsAttentionSection")}
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
{pendingApprovals.slice(0, 5).map((approval) => {
|
||||
const requester = agents.find((a) => a.id === approval.requestedByAgentId);
|
||||
{pendingApprovals.slice(0, 5).map((approval, i) => {
|
||||
const requester = approval.requestedByAgentId ? agentMap.get(approval.requestedByAgentId) : undefined;
|
||||
return (
|
||||
<button
|
||||
<motion.button
|
||||
key={approval.id}
|
||||
initial={{ opacity: 0, y: 6 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.22, delay: i * 0.04, ease: [0.16, 1, 0.3, 1] }}
|
||||
type="button"
|
||||
onClick={() => onNavigate("approvals", approval.id)}
|
||||
className="focus-ring flex w-full items-center gap-3 rounded-[8px] border border-[color:var(--warn)] border-opacity-20 bg-[color:var(--warn-soft)] px-4 py-3 text-left transition hover:border-opacity-40"
|
||||
@@ -131,11 +160,11 @@ export function Dashboard(props: DashboardProps) {
|
||||
</div>
|
||||
</div>
|
||||
<ArrowRight size={14} className="shrink-0 text-[color:var(--muted)]" />
|
||||
</button>
|
||||
</motion.button>
|
||||
);
|
||||
})}
|
||||
{failedRuns.slice(0, 3).map((run) => {
|
||||
const agent = agents.find((a) => a.id === run.agentId);
|
||||
const agent = agentMap.get(run.agentId);
|
||||
return (
|
||||
<button
|
||||
key={run.id}
|
||||
@@ -155,7 +184,7 @@ export function Dashboard(props: DashboardProps) {
|
||||
);
|
||||
})}
|
||||
{urgentMessages.slice(0, 3).map((msg) => {
|
||||
const sender = agents.find((a) => a.id === msg.fromAgentId);
|
||||
const sender = agentMap.get(msg.fromAgentId);
|
||||
return (
|
||||
<button
|
||||
key={msg.id}
|
||||
@@ -189,7 +218,7 @@ export function Dashboard(props: DashboardProps) {
|
||||
</button>
|
||||
</div>
|
||||
{activeWork.length > 0 ? (
|
||||
<div className="rounded-[10px] border border-[color:var(--line)] bg-[color:var(--panel)] divide-y divide-[color:var(--line)]">
|
||||
<div className="rounded-[8px] border border-[color:var(--line)] bg-[color:var(--panel)] divide-y divide-[color:var(--line)]">
|
||||
{activeWork.map(({ run, agent, task }) => (
|
||||
<button
|
||||
key={run.id}
|
||||
@@ -201,7 +230,7 @@ export function Dashboard(props: DashboardProps) {
|
||||
<Robot size={16} className="text-[color:var(--success)]" />
|
||||
{run.status === "running" ? (
|
||||
<span className="absolute -right-0.5 -top-0.5 h-2 w-2">
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[color:var(--success)] opacity-40" />
|
||||
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[color:var(--success)] opacity-40" style={{ willChange: "transform" }} />
|
||||
<span className="relative inline-flex h-1.5 w-1.5 rounded-full bg-[color:var(--success)]" />
|
||||
</span>
|
||||
) : null}
|
||||
@@ -218,7 +247,7 @@ export function Dashboard(props: DashboardProps) {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-[10px] border border-dashed border-[color:var(--line)] py-8 text-center">
|
||||
<div className="rounded-[8px] border border-dashed border-[color:var(--line)] py-8 text-center">
|
||||
<div className="text-[13px] text-[color:var(--muted)]">{t("dash.allQuiet")}</div>
|
||||
<div className="mt-1 text-[11px] text-[color:var(--muted)]">No tasks currently running</div>
|
||||
</div>
|
||||
@@ -232,7 +261,7 @@ export function Dashboard(props: DashboardProps) {
|
||||
{t("dash.recentActivity")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-h-[360px] overflow-y-auto rounded-[10px] border border-[color:var(--line)] bg-[color:var(--panel)] py-2">
|
||||
<div className="max-h-[360px] overflow-y-auto rounded-[8px] border border-[color:var(--line)] bg-[color:var(--panel)] py-2">
|
||||
<ActivityFeed activity={activity} agents={agents} limit={10} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in new issue
Block a user