全新UI语言

This commit is contained in:
2026-07-11 23:37:48 +08:00
parent 4dde861c99
commit 386a28709f
19 changed files with 1032 additions and 1931 deletions
+28 -168
View File
@@ -1,181 +1,41 @@
import Link from "next/link";
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 { 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() {
export default async function ActivityPage({ searchParams }: PageProps<"/activity">) {
const caregiver = await requireCaregiverSession("/activity");
const { usageEvents, conversationTurns, toolCalls } = await getCaregiverActivity(
caregiver.id,
);
const { usageEvents, conversationTurns, toolCalls } = await getCaregiverActivity(caregiver.id);
const rawTab = (await searchParams).tab;
const tab = rawTab === "chat" || rawTab === "tool" ? rawTab : "usage";
const usageChartData = aggregateByDate(usageEvents);
const conversationChartData = aggregateByDate(conversationTurns);
const rows = tab === "usage"
? usageEvents.map((item) => ({ id: item.id, top: getDeviceName(item.elderDevice.displayName), time: formatDateTime(item.createdAt), body: getUsageLabel(item.eventType) }))
: tab === "chat"
? conversationTurns.map((item) => ({ id: item.id, top: getConversationRoleLabel(item.role), time: formatDateTime(item.createdAt), body: truncateText(item.content, 180) }))
: toolCalls.map((item) => ({ id: item.id, top: item.toolName, time: formatDateTime(item.createdAt), body: truncateText(item.outputText || item.argumentsJson, 180) }));
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 currentPath="/activity" title="陪伴动态" caregiverLabel={getCaregiverDisplayName(caregiver)} backHref="/">
<div className="segment">
<Link href="/activity?tab=usage" className={tab === "usage" ? "active" : ""}>使用</Link>
<Link href="/activity?tab=chat" className={tab === "chat" ? "active" : ""}>对话</Link>
<Link href="/activity?tab=tool" className={tab === "tool" ? "active" : ""}>智能服务</Link>
</div>
<section className="card">
{rows.length ? rows.map((row) => (
<article key={row.id} className="timeline-item">
<div className="timeline-top" style={{ color: tab === "tool" ? "var(--accent)" : undefined }}>{row.top}<span className="timeline-time">{row.time}</span></div>
<p className="timeline-body">{row.body}</p>
</article>
)) : <div className="empty">这个分类暂时还没有动态。</div>}
</section>
<p className="muted-note">「智能服务」记录数字人助理自主调用的工具,包括照护知识查询与双向留言等。</p>
</PanelShell>
);
}
}