2026-04-17 12:58:38 +08:00

181 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { PanelShell } from "@/components/panel-shell";
import { ActivityChart } from "@/components/activity-chart";
import { getCaregiverActivity } from "@/lib/caregiver-panel";
import {
formatDateTime,
getConversationRoleLabel,
getDeviceName,
getUsageLabel,
truncateText,
} from "@/lib/panel-format";
import { requireCaregiverSession } from "@/lib/page-auth";
import { getCaregiverDisplayName } from "@/lib/session";
export const dynamic = "force-dynamic";
function aggregateByDate(items: { createdAt: Date | string }[]) {
const counts: Record<string, number> = {};
for (const item of items) {
const date = new Date(item.createdAt);
const key = `${date.getMonth() + 1}/${date.getDate()}`;
counts[key] = (counts[key] || 0) + 1;
}
return Object.entries(counts)
.map(([date, count]) => ({ date, count }))
.reverse();
}
export default async function ActivityPage() {
const caregiver = await requireCaregiverSession("/activity");
const { usageEvents, conversationTurns, toolCalls } = await getCaregiverActivity(
caregiver.id,
);
const usageChartData = aggregateByDate(usageEvents);
const conversationChartData = aggregateByDate(conversationTurns);
return (
<PanelShell
currentPath="/activity"
title="陪伴动态"
description="了解长辈最近的使用情况、聊天内容和智能服务记录。"
caregiverLabel={getCaregiverDisplayName(caregiver)}
>
{usageEvents.length === 0 && conversationTurns.length === 0 && toolCalls.length === 0 ? (
<section className="rounded-[32px] border border-dashed border-[var(--line)] bg-white/72 px-6 py-10 text-center shadow-[0_20px_50px_rgba(115,76,42,0.10)]">
<h2 className="font-[family-name:var(--font-display)] text-3xl text-[var(--ink)]">
</h2>
<p className="mx-auto mt-4 max-w-2xl text-base leading-8 text-[var(--muted)]">
使使便
</p>
</section>
) : (
<>
{(usageChartData.length > 1 || conversationChartData.length > 1) && (
<section className="grid gap-6 xl:grid-cols-2">
{usageChartData.length > 1 && (
<div className="rounded-[32px] border border-white/75 bg-white/82 p-6 shadow-[0_26px_60px_rgba(115,76,42,0.12)]">
<p className="text-sm font-medium tracking-[0.18em] text-[var(--copper)] uppercase">
使
</p>
<h2 className="mt-2 font-[family-name:var(--font-display)] text-2xl text-[var(--ink)]">
使
</h2>
<div className="mt-4">
<ActivityChart data={usageChartData} color="#c76733" />
</div>
</div>
)}
{conversationChartData.length > 1 && (
<div className="rounded-[32px] border border-white/75 bg-white/82 p-6 shadow-[0_26px_60px_rgba(115,76,42,0.12)]">
<p className="text-sm font-medium tracking-[0.18em] text-[var(--copper)] uppercase">
</p>
<h2 className="mt-2 font-[family-name:var(--font-display)] text-2xl text-[var(--ink)]">
</h2>
<div className="mt-4">
<ActivityChart data={conversationChartData} color="#76845e" />
</div>
</div>
)}
</section>
)}
<section className="grid gap-6 xl:grid-cols-3">
<div className="rounded-[32px] border border-white/75 bg-white/82 p-6 shadow-[0_26px_60px_rgba(115,76,42,0.12)]">
<p className="text-sm font-medium tracking-[0.18em] text-[var(--copper)] uppercase">
使
</p>
<h2 className="mt-2 font-[family-name:var(--font-display)] text-3xl text-[var(--ink)]">
</h2>
<div className="mt-5 space-y-3">
{usageEvents.length === 0 ? (
<p className="rounded-[24px] bg-[var(--paper-soft)] px-5 py-4 text-sm leading-7 text-[var(--muted)]">
使
</p>
) : (
usageEvents.map((event) => (
<div key={event.id} className="rounded-[24px] bg-[var(--paper-soft)] px-5 py-4">
<div className="flex items-center justify-between gap-3 text-xs text-[var(--muted)]">
<span>{getDeviceName(event.elderDevice.displayName)}</span>
<span>{formatDateTime(event.createdAt)}</span>
</div>
<p className="mt-2 text-sm leading-7 text-[var(--ink)]">
{getUsageLabel(event.eventType)}
</p>
</div>
))
)}
</div>
</div>
<div className="rounded-[32px] border border-white/75 bg-white/82 p-6 shadow-[0_26px_60px_rgba(115,76,42,0.12)]">
<p className="text-sm font-medium tracking-[0.18em] text-[var(--copper)] uppercase">
</p>
<h2 className="mt-2 font-[family-name:var(--font-display)] text-3xl text-[var(--ink)]">
</h2>
<div className="mt-5 space-y-3">
{conversationTurns.length === 0 ? (
<p className="rounded-[24px] bg-[var(--paper-soft)] px-5 py-4 text-sm leading-7 text-[var(--muted)]">
</p>
) : (
conversationTurns.map((turn) => (
<div key={turn.id} className="rounded-[24px] bg-[var(--paper-soft)] px-5 py-4">
<div className="flex items-center justify-between gap-3 text-xs text-[var(--muted)]">
<span>{getConversationRoleLabel(turn.role)}</span>
<span>{formatDateTime(turn.createdAt)}</span>
</div>
<p className="mt-2 text-xs text-[var(--muted)]">
{getDeviceName(turn.elderDevice.displayName)}
</p>
<p className="mt-2 text-sm leading-7 text-[var(--ink)]">
{truncateText(turn.content, 132)}
</p>
</div>
))
)}
</div>
</div>
<div className="rounded-[32px] border border-white/75 bg-white/82 p-6 shadow-[0_26px_60px_rgba(115,76,42,0.12)]">
<p className="text-sm font-medium tracking-[0.18em] text-[var(--copper)] uppercase">
</p>
<h2 className="mt-2 font-[family-name:var(--font-display)] text-3xl text-[var(--ink)]">
</h2>
<div className="mt-5 space-y-3">
{toolCalls.length === 0 ? (
<p className="rounded-[24px] bg-[var(--paper-soft)] px-5 py-4 text-sm leading-7 text-[var(--muted)]">
</p>
) : (
toolCalls.map((log) => (
<div key={log.id} className="rounded-[24px] bg-[var(--paper-soft)] px-5 py-4">
<div className="flex items-center justify-between gap-3 text-xs text-[var(--muted)]">
<span>{getDeviceName(log.elderDevice.displayName)}</span>
<span>{formatDateTime(log.createdAt)}</span>
</div>
<p className="mt-2 text-sm font-semibold text-[var(--ink)]">
{log.toolName}
</p>
<p className="mt-2 text-sm leading-7 text-[var(--muted)]">
{truncateText(log.outputText || log.argumentsJson, 136)}
</p>
</div>
))
)}
</div>
</div>
</section>
</>
)}
</PanelShell>
);
}