diff --git a/AgentCompany-New/src/renderer/src/pages/HomePage.tsx b/AgentCompany-New/src/renderer/src/pages/HomePage.tsx
index 4a9dbe57..23a3f083 100644
--- a/AgentCompany-New/src/renderer/src/pages/HomePage.tsx
+++ b/AgentCompany-New/src/renderer/src/pages/HomePage.tsx
@@ -12,6 +12,7 @@ import type {
TaskRunRecord,
} from "../../../shared/domain";
import { NewTaskDialog } from "./NewTaskDialog";
+import { AgentWorkloadChart, BudgetUtilizationChart, CostTrendChart, RunActivityChart, SuccessRateChart } from "./home/DashboardCharts";
import type { PageProps } from "./types";
import { activeOrgId, formatRelative, formatTime, money, requireActiveEmployee, requireActiveOrganization } from "./types";
@@ -37,6 +38,7 @@ export function HomePage({ snapshot, actions, navigate }: PageProps) {
const taskRuns = useMemo(() => snapshot.taskRuns.filter((run) => run.orgId === orgId), [snapshot.taskRuns, orgId]);
const chatMessages = useMemo(() => snapshot.chatMessages.filter((m) => m.orgId === orgId), [snapshot.chatMessages, orgId]);
const chatSpaces = useMemo(() => snapshot.chatSpaces.filter((s) => s.orgId === orgId), [snapshot.chatSpaces, orgId]);
+ const providerRequests = useMemo(() => snapshot.providerRequests.filter((r) => r.orgId === orgId), [snapshot.providerRequests, orgId]);
const [newTaskOpen, setNewTaskOpen] = useState(false);
const pendingApprovals = approvals.filter((approval) => approval.state === "pending");
@@ -82,8 +84,16 @@ export function HomePage({ snapshot, actions, navigate }: PageProps) {
+
+
- navigate("work")} />
+ navigate("runs")} />
navigate("chat")} />
diff --git a/AgentCompany-New/src/renderer/src/pages/home/DashboardCharts.tsx b/AgentCompany-New/src/renderer/src/pages/home/DashboardCharts.tsx
new file mode 100644
index 00000000..5b77a392
--- /dev/null
+++ b/AgentCompany-New/src/renderer/src/pages/home/DashboardCharts.tsx
@@ -0,0 +1,179 @@
+import type { AgentRecord, ProviderRecord, TaskRecord, TaskRunRecord } from "../../../../shared/domain";
+import { TASK_STATUS } from "../../../../shared/domain";
+
+const CHART_H = 120;
+const CHART_W = 260;
+const BAR_GAP = 4;
+
+export function AgentWorkloadChart({ agents, tasks }: { agents: AgentRecord[]; tasks: TaskRecord[] }) {
+ const data = agents.slice(0, 8).map((a) => ({
+ name: a.name.split(/\s+/)[0],
+ count: tasks.filter((t) => t.assigneeAgentId === a.id && t.status !== TASK_STATUS.DONE).length,
+ }));
+ const max = Math.max(1, ...data.map((d) => d.count));
+ const barW = data.length > 0 ? (CHART_W - (data.length - 1) * BAR_GAP) / data.length : 0;
+
+ return (
+
+
+
+ );
+}
+
+export function BudgetUtilizationChart({ agents, providers }: { agents: AgentRecord[]; providers: ProviderRecord[] }) {
+ const connected = providers.filter((p) => p.enabled);
+ const data = connected.slice(0, 6).map((p) => {
+ const allocated = agents.filter((a) => a.modelProviderId === p.id).reduce((s, a) => s + a.budgetCents, 0);
+ return { label: p.label.split(/\s+/)[0], cents: allocated };
+ });
+ const total = Math.max(1, data.reduce((s, d) => s + d.cents, 0));
+
+ return (
+
+
+
+ );
+}
+
+export function RunActivityChart({ taskRuns }: { taskRuns: TaskRunRecord[] }) {
+ const buckets = last7DayBuckets(taskRuns.map((r) => r.createdAt));
+ const max = Math.max(1, ...buckets.map((b) => b.count));
+ const barW = (CHART_W - 6 * BAR_GAP) / 7;
+
+ return (
+
+
+
+ );
+}
+
+export function SuccessRateChart({ taskRuns }: { taskRuns: TaskRunRecord[] }) {
+ const completed = taskRuns.filter((r) => r.state === "completed").length;
+ const failed = taskRuns.filter((r) => r.state === "failed").length;
+ const total = completed + failed;
+ const rate = total > 0 ? Math.round((completed / total) * 100) : 0;
+ const circumference = 2 * Math.PI * 42;
+ const offset = circumference - (rate / 100) * circumference;
+
+ return (
+
+
+
+ {completed} passed ยท {failed} failed
+
+
+ );
+}
+
+export function CostTrendChart({ providerRequests }: { providerRequests: Array<{ createdAt: string; inputCharCount: number; outputCharCount: number }> }) {
+ const buckets = last7DayBuckets(providerRequests.map((r) => r.createdAt));
+ const charBuckets = buckets.map((b, i) => {
+ const dayStart = dayStartMs(i);
+ const dayEnd = dayStart + 86400000;
+ const dayRequests = providerRequests.filter((r) => {
+ const t = new Date(r.createdAt).getTime();
+ return t >= dayStart && t < dayEnd;
+ });
+ return { ...b, chars: dayRequests.reduce((s, r) => s + r.inputCharCount + r.outputCharCount, 0) };
+ });
+ const max = Math.max(1, ...charBuckets.map((b) => b.chars));
+ const points = charBuckets.map((b, i) => {
+ const x = (i / 6) * (CHART_W - 10) + 5;
+ const y = CHART_H - 20 - (b.chars / max) * (CHART_H - 30);
+ return `${x},${y}`;
+ }).join(" ");
+
+ return (
+
+
+
+ );
+}
+
+function ChartCard({ title, children }: { title: string; children: React.ReactNode }) {
+ return (
+
+ );
+}
+
+function last7DayBuckets(dates: string[]): Array<{ label: string; count: number }> {
+ const now = Date.now();
+ const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ return Array.from({ length: 7 }, (_, i) => {
+ const offset = 6 - i;
+ const start = new Date(now - offset * 86400000);
+ start.setHours(0, 0, 0, 0);
+ const end = new Date(start.getTime() + 86400000);
+ const count = dates.filter((d) => {
+ const t = new Date(d).getTime();
+ return t >= start.getTime() && t < end.getTime();
+ }).length;
+ return { label: dayNames[start.getDay()], count };
+ });
+}
+
+function dayStartMs(bucketIndex: number): number {
+ const offset = 6 - bucketIndex;
+ const start = new Date(Date.now() - offset * 86400000);
+ start.setHours(0, 0, 0, 0);
+ return start.getTime();
+}