diff --git a/app/activity/page.tsx b/app/activity/page.tsx index 49ddf43..347dddf 100644 --- a/app/activity/page.tsx +++ b/app/activity/page.tsx @@ -1,4 +1,5 @@ import { PanelShell } from "@/components/panel-shell"; +import { ActivityChart } from "@/components/activity-chart"; import { getCaregiverActivity } from "@/lib/caregiver-panel"; import { formatDateTime, @@ -11,102 +12,168 @@ import { requireCaregiverSession } from "@/lib/page-auth"; export const dynamic = "force-dynamic"; +function aggregateByDate(items: { createdAt: Date | string }[]) { + const counts: Record = {}; + 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 ( {usageEvents.length === 0 && conversationTurns.length === 0 && toolCalls.length === 0 ? (

- 还没有活动记录 + 暂时没有动态

- 等老人端开始对话、切换镜头或触发桥接工具后,这里会自动累积活动时间线。 + 长辈开始使用智能助理后,这里会自动记录使用情况和聊天内容,方便您随时了解。

) : ( -
-
-

- 使用事件 -

-

- 老人端操作轨迹 -

-
- {usageEvents.map((event) => ( -
-
- {getDeviceName(event.elderDevice.displayName)} - {formatDateTime(event.createdAt)} -
-

- {getUsageLabel(event.eventType)} + <> + {(usageChartData.length > 1 || conversationChartData.length > 1) && ( +

+ {usageChartData.length > 1 && ( +
+

+ 使用频率

+

+ 最近使用趋势 +

+
+ +
- ))} -
-
+ )} + {conversationChartData.length > 1 && ( +
+

+ 聊天频率 +

+

+ 最近对话趋势 +

+
+ +
+
+ )} +
+ )} -
-

- 对话片段 -

-

- 最近说了什么 -

-
- {conversationTurns.map((turn) => ( -
-
- {getConversationRoleLabel(turn.role)} - {formatDateTime(turn.createdAt)} -
-

- {getDeviceName(turn.elderDevice.displayName)} +

+
+

+ 使用记录 +

+

+ 最近的操作 +

+
+ {usageEvents.length === 0 ? ( +

+ 暂时没有使用记录。

-

- {truncateText(turn.content, 132)} -

-
- ))} + ) : ( + usageEvents.map((event) => ( +
+
+ {getDeviceName(event.elderDevice.displayName)} + {formatDateTime(event.createdAt)} +
+

+ {getUsageLabel(event.eventType)} +

+
+ )) + )} +
-
-
-

- 工具调用 -

-

- function calling 执行记录 -

-
- {toolCalls.map((log) => ( -
-
- {getDeviceName(log.elderDevice.displayName)} - {formatDateTime(log.createdAt)} -
-

- {log.toolName} +

+

+ 聊天记录 +

+

+ 最近的对话 +

+
+ {conversationTurns.length === 0 ? ( +

+ 暂时没有聊天记录。

-

- {truncateText(log.outputText || log.argumentsJson, 136)} -

-
- ))} + ) : ( + conversationTurns.map((turn) => ( +
+
+ {getConversationRoleLabel(turn.role)} + {formatDateTime(turn.createdAt)} +
+

+ {getDeviceName(turn.elderDevice.displayName)} +

+

+ {truncateText(turn.content, 132)} +

+
+ )) + )} +
-
- + +
+

+ 智能服务 +

+

+ 助理做了什么 +

+
+ {toolCalls.length === 0 ? ( +

+ 暂时没有智能服务记录。 +

+ ) : ( + toolCalls.map((log) => ( +
+
+ {getDeviceName(log.elderDevice.displayName)} + {formatDateTime(log.createdAt)} +
+

+ {log.toolName} +

+

+ {truncateText(log.outputText || log.argumentsJson, 136)} +

+
+ )) + )} +
+
+ + )} ); diff --git a/app/bind/page.tsx b/app/bind/page.tsx index d234464..34972e8 100644 --- a/app/bind/page.tsx +++ b/app/bind/page.tsx @@ -27,13 +27,13 @@ export default async function BindPage({ searchParams }: BindPageProps) {

- 连接家人设备 + 连接设备

- 还没有收到设备码 + 还没有收到设备信息

- 请让老人打开家人连接页,把二维码给您扫一下,或者把设备码复制过来。 + 请让长辈打开手机上的「家人连接」页面,把二维码给您扫一下,或将设备码复制过来。

- 回到设备页 + 返回设备页
@@ -70,17 +70,17 @@ export default async function BindPage({ searchParams }: BindPageProps) {

- 绑定完成 + 连接成功

- 这位老人已经连到当前家属账号 + 已成功连接长辈的设备

- 现在您可以在控制台查看老人给家里的留言、最近陪伴记录,也能马上给老人写下一句问候。 + 现在可以查看长辈的留言、了解使用动态,也可以随时给长辈写一句问候。

-

设备 UUID

+

设备标识

{device.deviceUuid}

@@ -91,13 +91,13 @@ export default async function BindPage({ searchParams }: BindPageProps) { href={`/devices/${device.deviceUuid}`} className="inline-flex h-12 items-center justify-center rounded-full bg-[var(--copper)] px-5 text-sm font-semibold text-white transition hover:bg-[var(--copper-deep)]" > - 打开这台设备页 + 查看设备详情 - 查看全部设备 + 返回设备列表
diff --git a/app/devices/[deviceUuid]/page.tsx b/app/devices/[deviceUuid]/page.tsx index d6a228f..f56222e 100644 --- a/app/devices/[deviceUuid]/page.tsx +++ b/app/devices/[deviceUuid]/page.tsx @@ -41,18 +41,18 @@ export default async function DeviceDetailPage({ params }: DeviceDetailPageProps
-

老人新留言

+

新留言

{binding.elderUnreadCount}

-

待老人查看

+

待查看

{binding.familyUnreadCount}

@@ -64,7 +64,7 @@ export default async function DeviceDetailPage({ params }: DeviceDetailPageProps

-

绑定链接

+

设备链接

{truncateText(binding.bindUrl, 52)}

@@ -79,24 +79,24 @@ export default async function DeviceDetailPage({ params }: DeviceDetailPageProps

- 老人给家里的话 + 长辈的话

- 最近通过桥接工具送出的内容 + 最近收到的留言

- 去留言中心 + 全部留言
{elderMessages.length === 0 ? (

- 这台设备暂时还没有新的老人留言。 + 还没有收到长辈的留言。

) : ( elderMessages.map((message) => ( @@ -126,13 +126,13 @@ export default async function DeviceDetailPage({ params }: DeviceDetailPageProps 最近活动

- 这台设备刚刚发生了什么 + 最近的使用情况

{binding.elderDevice.usageEvents.length === 0 ? (

- 暂时还没有同步到新的使用事件。 + 暂时还没有使用记录。

) : ( binding.elderDevice.usageEvents.map((event) => ( @@ -151,16 +151,16 @@ export default async function DeviceDetailPage({ params }: DeviceDetailPageProps

- 家属给老人的问候 + 我的问候

- 最近写给这位老人的内容 + 已发出的留言

{familyMessages.length === 0 ? (

- 还没有发给这位老人的问候。 + 还没有给这位长辈写过留言。

) : ( familyMessages.map((message) => ( @@ -190,13 +190,13 @@ export default async function DeviceDetailPage({ params }: DeviceDetailPageProps 最近对话片段

- 快速回看陪伴内容 + 最近的聊天记录

{binding.elderDevice.conversationTurns.length === 0 ? (

- 这台设备最近还没有同步到对话片段。 + 还没有聊天记录。

) : ( binding.elderDevice.conversationTurns.map((turn) => ( diff --git a/app/devices/page.tsx b/app/devices/page.tsx index 3471e8f..c854991 100644 --- a/app/devices/page.tsx +++ b/app/devices/page.tsx @@ -16,8 +16,8 @@ export default async function DevicesPage() { return ( } > @@ -26,15 +26,15 @@ export default async function DevicesPage() {

- 设备入口说明 + 使用说明

- 一台设备一个详情页 + 如何连接长辈的设备?

-

1. 先通过扫码或粘贴设备码建立连接。

-

2. 进入设备详情页给这位老人写留言,不再在首页里混排。

-

3. 每台设备都有自己的最近留言、对话片段和工具调用摘要。

+

1. 让长辈打开手机上的「家人连接」页面,屏幕会显示一个二维码。

+

2. 您用自己的手机扫描这个二维码,或将设备码粘贴到左侧输入框。

+

3. 连接成功后,即可在这里查看长辈的留言、对话和使用动态。

@@ -42,13 +42,13 @@ export default async function DevicesPage() { href="/scan" className="inline-flex h-12 items-center justify-center rounded-full bg-[var(--olive)] px-5 text-sm font-semibold text-white transition hover:bg-[var(--olive-deep)]" > - 打开扫码页 + 扫码连接 - 去留言中心 + 查看留言
@@ -57,10 +57,10 @@ export default async function DevicesPage() { {devices.length === 0 ? (

- 还没有绑定设备 + 还没有连接设备

- 等待绑定成功后,每台设备都会在这里生成自己的专属入口,后续写留言和查看状态都从设备详情页进入。 + 扫描长辈手机上的二维码,或者在上方粘贴设备码,即可开始接收长辈的留言和动态。

) : ( @@ -74,7 +74,7 @@ export default async function DevicesPage() { className="rounded-[32px] border border-white/75 bg-white/82 p-6 shadow-[0_26px_60px_rgba(115,76,42,0.12)]" >

- 设备详情 + 已连接

{getDeviceName(binding.elderDevice.displayName)} @@ -85,28 +85,28 @@ export default async function DevicesPage() { 最近在线:{formatDateTime(binding.elderDevice.lastSeenAt)} - 老人新留言 {binding.elderUnreadCount} + 新留言 {binding.elderUnreadCount} - 待老人查看 {binding.familyUnreadCount} + 待查看 {binding.familyUnreadCount}

-

留言总数

+

留言

{binding.elderDevice._count.messages}

-

对话条数

+

对话

{binding.elderDevice._count.conversationTurns}

-

工具调用

+

智能服务

{binding.elderDevice._count.toolCalls}

@@ -114,11 +114,11 @@ export default async function DevicesPage() {
-

最近一条动态

+

最新动态

{latestMessage ? truncateText(latestMessage.content, 96) - : "这台设备还没有同步到新的留言或问候。"} + : "还没有收到新的留言或问候。"}

@@ -127,13 +127,13 @@ export default async function DevicesPage() { href={`/devices/${binding.elderDevice.deviceUuid}`} className="inline-flex h-12 items-center justify-center rounded-full bg-[var(--copper)] px-5 text-sm font-semibold text-white transition hover:bg-[var(--copper-deep)]" > - 打开设备页 + 查看详情 - 去留言中心 + 查看留言
diff --git a/app/globals.css b/app/globals.css index 1ba15ac..b50d60c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -38,7 +38,6 @@ body { } a { - color: inherit; text-decoration: none; } diff --git a/app/layout.tsx b/app/layout.tsx index 3119be1..c72ef80 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -17,7 +17,7 @@ const displayFont = Noto_Serif_SC({ export const metadata: Metadata = { metadataBase: new URL("https://digital-human.xn--876a.net"), title: "家人连线台", - description: "给老人设备做绑定、查看留言、追踪陪伴记录的家属端。", + description: "随时了解长辈的近况,查看留言、使用动态,给长辈写问候。", manifest: "/manifest.webmanifest", icons: { icon: [ diff --git a/app/manifest.ts b/app/manifest.ts index b8f181b..9357735 100644 --- a/app/manifest.ts +++ b/app/manifest.ts @@ -4,7 +4,7 @@ export default function manifest(): MetadataRoute.Manifest { return { name: "家人连线台", short_name: "家人连线台", - description: "给老人设备做绑定、查看留言、追踪陪伴记录的家属端。", + description: "随时了解长辈的近况,查看留言、使用动态,给长辈写问候。", start_url: "/", display: "standalone", background_color: "#f6eee1", diff --git a/app/messages/[messageId]/page.tsx b/app/messages/[messageId]/page.tsx index d2e3f69..73d9896 100644 --- a/app/messages/[messageId]/page.tsx +++ b/app/messages/[messageId]/page.tsx @@ -37,7 +37,7 @@ export default async function MessageDetailPage({ params }: MessageDetailPagePro
@@ -70,7 +70,7 @@ export default async function MessageDetailPage({ params }: MessageDetailPagePro diff --git a/app/messages/page.tsx b/app/messages/page.tsx index 195a31d..0fbd788 100644 --- a/app/messages/page.tsx +++ b/app/messages/page.tsx @@ -31,8 +31,8 @@ export default async function MessagesPage() { return ( } > @@ -44,13 +44,13 @@ export default async function MessagesPage() {

-

老人给家里的未读留言

+

长辈的未读留言

{unreadIncomingCount}

-

待老人查看的家属留言

+

待长辈查看

{unreadOutgoingCount}

@@ -60,23 +60,23 @@ export default async function MessagesPage() { {messages.length === 0 ? (

- 还没有留言记录 + 还没有留言

- 去设备页给老人留一句问候,或等待老人通过桥接工具给家里捎话;新记录会自动出现在这里,并带有可分享的消息编号。 + 连接长辈的设备后,就可以在这里查看长辈的留言,也可以给长辈写问候。

- 去设备页 + 连接设备 - 去扫码绑定 + 扫码连接
@@ -84,13 +84,13 @@ export default async function MessagesPage() {
{[ { - title: "老人给家里的留言", - subtitle: "收到新话时也会从这里直达详情页。", + title: "长辈的留言", + subtitle: "长辈通过智能助理捆来的话。", items: incomingMessages, }, { - title: "家属给老人的问候", - subtitle: "写出去的每条问候也有独立 URL,可回看送达状态。", + title: "我发出的问候", + subtitle: "您写给长辈的每一条问候。", items: outgoingMessages, }, ].map((section) => ( @@ -99,7 +99,7 @@ export default async function MessagesPage() { className="rounded-[32px] border border-white/75 bg-white/80 p-6 shadow-[0_26px_60px_rgba(115,76,42,0.12)]" >

- 留言列表 + {section.title === "长辈的留言" ? "收到的" : "发出的"}

{section.title} diff --git a/app/page.tsx b/app/page.tsx index 2d35787..a34e8e2 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,4 +1,5 @@ import Link from "next/link"; +import { MessageCircle, Smartphone, Activity, ScanLine } from "lucide-react"; import { PanelShell } from "@/components/panel-shell"; import { PwaControls } from "@/components/pwa-controls"; @@ -35,32 +36,32 @@ export default async function Home() { return ( } >
-

已连接老人设备

+

已连接设备

{dashboard.devices.length}

-

老人给家里的未读留言

+

长辈的新留言

{totalUnreadForCaregiver}

-

待老人查看的家属留言

+

待长辈查看

{totalUnreadForElder}

-

最近存档对话条数

+

陪伴对话

{totalConversations}

@@ -71,69 +72,77 @@ export default async function Home() { {[ { href: "/messages", - title: "留言中心", - text: "集中看所有老人留言和家属留言,每条消息都有 URL 编号可直接打开。", + title: "留言板", + text: "查看长辈捎来的话,也能回看您发出的每一条问候。", tone: "bg-[rgba(53,84,141,0.12)]", + icon: MessageCircle, }, { href: "/devices", - title: "设备与绑定", - text: "去管理设备、扫码绑定、查看某一台设备的专属详情页和留言入口。", + title: "我的设备", + text: "管理已绑定的长辈设备,进入设备详情给长辈写留言。", tone: "bg-[rgba(118,132,94,0.14)]", + icon: Smartphone, }, { href: "/activity", - title: "活动回看", - text: "把使用事件、工具调用和对话片段拆出来,回看时不再和留言混在一起。", + title: "陪伴动态", + text: "了解长辈最近的使用情况和聊天记录,安心守护。", tone: "bg-[rgba(199,103,51,0.12)]", + icon: Activity, }, { href: "/scan", title: "扫码绑定", - text: "拿起另一台手机时,直接走扫码页,不必再回首页寻找入口。", + text: "扫描长辈手机上的二维码,一步完成设备连接。", tone: "bg-[rgba(36,27,20,0.08)]", + icon: ScanLine, }, - ].map((item) => ( - -
- 跳转路径 -
-

- {item.title} -

-

{item.text}

-

前往 {item.href}

- - ))} + ].map((item) => { + const Icon = item.icon; + return ( + +
+ + {item.title} +
+

+ {item.title} +

+

{item.text}

+

查看详情 →

+ + ); + })}
{dashboard.devices.length === 0 ? (

- 还没有绑定老人设备 + 开始使用

- 先去设备页或扫码页建立第一条连接 + 先连接长辈的设备

- 绑定完成后,留言中心会出现 URL 编号消息卡,活动页会出现陪伴记录,推送也能直达具体留言。 + 让长辈打开手机里的「家人连接」页面,用您的手机扫描二维码,即可建立连接。连接成功后,您就能收到长辈的留言和使用动态。

- 去设备页 + 扫码连接 - 直接扫码 + 手动添加
@@ -143,24 +152,24 @@ export default async function Home() {

- 新收到的老人留言 + 长辈的留言

- 点一下就能按编号直达 + 最近收到的话

- 查看全部留言 + 查看全部
{recentIncomingMessages.length === 0 ? (

- 目前还没有新的老人留言。 + 暂时还没有收到新留言。

) : ( recentIncomingMessages.map((message) => ( @@ -190,24 +199,24 @@ export default async function Home() {

- 最近发给老人的问候 + 我发出的问候

- 发出后也能按编号回看 + 最近写给长辈的话

- 去设备页留言 + 写新留言
{recentOutgoingMessages.length === 0 ? (

- 还没有新的家属留言,可以到设备页针对某一台设备写下问候。 + 还没有给长辈留过言,去设备页写一句问候吧。

) : ( recentOutgoingMessages.map((message) => ( diff --git a/app/scan/page.tsx b/app/scan/page.tsx index 6f2fec0..fb10b40 100644 --- a/app/scan/page.tsx +++ b/app/scan/page.tsx @@ -8,19 +8,19 @@ export default function ScanPage() {

- 扫码绑定 + 扫码连接

- 对准老人手机里的家人连接二维码 + 对准长辈手机上的二维码

- 扫描成功后,系统会自动把老人的本机 UUID 绑定到当前家属账号。之后,您就能看见老人的留言、最近对话和使用记录。 + 扫描成功后,系统会自动将长辈的设备连接到您的账号。之后就能随时查看长辈的留言和使用动态。

-

1. 让老人打开安卓端的家人连接页。

-

2. 把镜头对准屏幕上的二维码,停稳一两秒。

-

3. 绑定成功后会自动跳回控制台。

+

1. 让长辈打开手机上的「家人连接」页面。

+

2. 将镜头对准屏幕上的二维码,保持一两秒即可。

+

3. 连接成功后会自动跳转。

@@ -28,7 +28,7 @@ export default function ScanPage() { href="/devices" className="inline-flex h-12 items-center justify-center rounded-full border border-[var(--line)] bg-white px-5 text-sm font-semibold text-[var(--ink)] transition hover:bg-[var(--paper-soft)]" > - 回到设备页 + 返回设备页
diff --git a/components/activity-chart.tsx b/components/activity-chart.tsx new file mode 100644 index 0000000..6df1e90 --- /dev/null +++ b/components/activity-chart.tsx @@ -0,0 +1,65 @@ +"use client"; + +import { + BarChart, + Bar, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer, + CartesianGrid, +} from "recharts"; + +type DailyCount = { + date: string; + count: number; +}; + +type ActivityChartProps = { + data: DailyCount[]; + color?: string; +}; + +export function ActivityChart({ data, color = "var(--copper)" }: ActivityChartProps) { + if (data.length === 0) { + return null; + } + + return ( +
+ + + + + + [`${value} 次`, "次数"]} + /> + + + +
+ ); +} diff --git a/components/dashboard-actions.tsx b/components/dashboard-actions.tsx index 0b5ae71..2720cff 100644 --- a/components/dashboard-actions.tsx +++ b/components/dashboard-actions.tsx @@ -14,7 +14,7 @@ export function BindDeviceForm() { event.preventDefault(); if (!rawCode.trim()) { - setNotice("先把老人二维码里的设备码贴进来。\n"); + setNotice("请先粘贴长辈手机上的设备码。\n"); return; } @@ -38,7 +38,7 @@ export function BindDeviceForm() { throw new Error(payload?.error || "连接失败,请稍后再试。"); } - setNotice("已经连上了,这位老人的留言和陪伴记录会出现在下方。\n"); + setNotice("连接成功!现在可以查看长辈的留言和动态了。\n"); setRawCode(""); startTransition(() => { router.refresh(); @@ -57,20 +57,20 @@ export function BindDeviceForm() {

- 家人连接 + 添加设备

- 扫二维码,或者直接贴入设备码 + 扫码或粘贴设备码连接长辈

- 如果老人已经把二维码发给您,直接扫码最快;如果您拿到的是一段链接或设备码,也可以贴到下面。 + 让长辈打开手机上的「家人连接」页面,扫描二维码最快;也可以将设备码粘贴到下方。

- 打开扫码页 + 扫码连接
@@ -78,7 +78,7 @@ export function BindDeviceForm() { setRawCode(event.target.value)} - placeholder="粘贴二维码链接,或者直接贴设备 UUID" + placeholder="粘贴设备码或二维码链接" className="h-13 flex-1 rounded-full border border-[var(--line)] bg-[var(--paper-soft)] px-5 text-sm text-[var(--ink)] outline-none transition focus:border-[var(--copper)] focus:bg-white" />