digital-human-monitor-v2/lib/caregiver-panel.ts

397 lines
9.0 KiB
TypeScript

import { MessageDirection, MessageStatus } from "@prisma/client";
import { createBindUrl, getCaregiverDashboard } from "@/lib/monitor-data";
import { prisma } from "@/lib/prisma";
const deviceSummarySelect = {
id: true,
deviceUuid: true,
displayName: true,
appVersion: true,
lastSeenAt: true,
} as const;
async function loadCaregiverDeviceNameMap(
caregiverId: string,
elderDeviceIds: string[],
) {
if (elderDeviceIds.length === 0) {
return new Map<string, string>();
}
const bindings = await prisma.deviceBinding.findMany({
where: {
caregiverId,
elderDeviceId: {
in: elderDeviceIds,
},
},
select: {
elderDeviceId: true,
alias: true,
elderDevice: {
select: {
displayName: true,
},
},
},
});
return new Map(
bindings
.map((binding) => [
binding.elderDeviceId,
binding.alias?.trim() || binding.elderDevice.displayName?.trim() || "",
] as const)
.filter((entry) => entry[1].length > 0),
);
}
function applyCaregiverDeviceNames<
T extends {
elderDeviceId: string;
elderDevice: {
displayName: string | null;
};
},
>(items: T[], deviceNameMap: Map<string, string>) {
items.forEach((item) => {
const resolvedName = deviceNameMap.get(item.elderDeviceId);
if (resolvedName) {
item.elderDevice.displayName = resolvedName;
}
});
return items;
}
export async function getCaregiverOverview(caregiverId: string) {
const dashboard = await getCaregiverDashboard(caregiverId);
const [recentIncomingMessages, recentOutgoingMessages] = await prisma.$transaction([
prisma.familyMessage.findMany({
where: {
direction: MessageDirection.ELDER_TO_FAMILY,
elderDevice: {
bindings: {
some: { caregiverId },
},
},
},
include: {
elderDevice: {
select: deviceSummarySelect,
},
},
orderBy: { createdAt: "desc" },
take: 4,
}),
prisma.familyMessage.findMany({
where: {
direction: MessageDirection.FAMILY_TO_ELDER,
caregiverId,
elderDevice: {
bindings: {
some: { caregiverId },
},
},
},
include: {
elderDevice: {
select: deviceSummarySelect,
},
},
orderBy: { createdAt: "desc" },
take: 4,
}),
]);
const deviceNameMap = new Map(
dashboard.devices
.map((device) => [device.elderDeviceId, device.elderDevice.displayName?.trim() || ""] as const)
.filter((entry) => entry[1].length > 0),
);
applyCaregiverDeviceNames(recentIncomingMessages, deviceNameMap);
applyCaregiverDeviceNames(recentOutgoingMessages, deviceNameMap);
return {
dashboard,
recentIncomingMessages,
recentOutgoingMessages,
};
}
export async function getCaregiverMessages(caregiverId: string) {
const [messages, unreadIncomingCount, unreadOutgoingCount] = await prisma.$transaction([
prisma.familyMessage.findMany({
where: {
elderDevice: {
bindings: {
some: { caregiverId },
},
},
},
include: {
elderDevice: {
select: deviceSummarySelect,
},
},
orderBy: [{ createdAt: "desc" }, { publicId: "desc" }],
take: 120,
}),
prisma.familyMessage.count({
where: {
direction: MessageDirection.ELDER_TO_FAMILY,
elderDevice: {
bindings: {
some: { caregiverId },
},
},
readAt: null,
},
}),
prisma.familyMessage.count({
where: {
direction: MessageDirection.FAMILY_TO_ELDER,
caregiverId,
elderDevice: {
bindings: {
some: { caregiverId },
},
},
readAt: null,
},
}),
]);
const deviceNameMap = await loadCaregiverDeviceNameMap(
caregiverId,
[...new Set(messages.map((message) => message.elderDeviceId))],
);
applyCaregiverDeviceNames(messages, deviceNameMap);
return {
messages,
unreadIncomingCount,
unreadOutgoingCount,
};
}
export async function getCaregiverMessageDetail(
caregiverId: string,
publicId: number,
) {
let message = await prisma.familyMessage.findFirst({
where: {
publicId,
elderDevice: {
bindings: {
some: { caregiverId },
},
},
},
include: {
elderDevice: {
select: deviceSummarySelect,
},
},
});
if (!message) {
return null;
}
if (message.direction === MessageDirection.ELDER_TO_FAMILY && !message.readAt) {
const now = new Date();
message = await prisma.familyMessage.update({
where: { id: message.id },
data: {
readAt: now,
deliveredAt: message.deliveredAt ?? now,
status: MessageStatus.READ,
},
include: {
elderDevice: {
select: deviceSummarySelect,
},
},
});
}
const deviceNameMap = await loadCaregiverDeviceNameMap(caregiverId, [message.elderDeviceId]);
applyCaregiverDeviceNames([message], deviceNameMap);
return message;
}
export async function getCaregiverDevices(caregiverId: string) {
const dashboard = await getCaregiverDashboard(caregiverId);
return dashboard.devices;
}
export async function getCaregiverDeviceDetail(
caregiverId: string,
deviceUuid: string,
) {
const binding = await prisma.deviceBinding.findFirst({
where: {
caregiverId,
elderDevice: {
deviceUuid,
},
},
include: {
elderDevice: {
include: {
messages: {
orderBy: [{ createdAt: "desc" }, { publicId: "desc" }],
take: 14,
},
usageEvents: {
orderBy: { createdAt: "desc" },
take: 10,
},
conversationTurns: {
orderBy: { createdAt: "desc" },
take: 12,
},
toolCalls: {
orderBy: { createdAt: "desc" },
take: 8,
},
_count: {
select: {
messages: true,
usageEvents: true,
conversationTurns: true,
toolCalls: true,
},
},
},
},
},
});
if (!binding) {
return null;
}
const [familyUnreadCount, elderUnreadCount] = await prisma.$transaction([
prisma.familyMessage.count({
where: {
elderDeviceId: binding.elderDeviceId,
direction: MessageDirection.FAMILY_TO_ELDER,
readAt: null,
},
}),
prisma.familyMessage.count({
where: {
elderDeviceId: binding.elderDeviceId,
direction: MessageDirection.ELDER_TO_FAMILY,
readAt: null,
},
}),
]);
return {
...binding,
elderDevice: {
...binding.elderDevice,
displayName:
binding.alias?.trim() || binding.elderDevice.displayName?.trim() || null,
},
bindUrl: createBindUrl(binding.elderDevice.deviceUuid),
familyUnreadCount,
elderUnreadCount,
};
}
export async function getCaregiverActivity(caregiverId: string) {
const deviceBindings = await prisma.deviceBinding.findMany({
where: { caregiverId },
select: {
elderDeviceId: true,
},
});
const elderDeviceIds = deviceBindings.map((binding) => binding.elderDeviceId);
if (elderDeviceIds.length === 0) {
return {
usageEvents: [],
conversationTurns: [],
toolCalls: [],
};
}
const [usageEvents, conversationTurns, toolCalls] = await prisma.$transaction([
prisma.usageEvent.findMany({
where: {
elderDeviceId: { in: elderDeviceIds },
},
include: {
elderDevice: {
select: deviceSummarySelect,
},
},
orderBy: { createdAt: "desc" },
take: 24,
}),
prisma.conversationTurn.findMany({
where: {
elderDeviceId: { in: elderDeviceIds },
},
include: {
elderDevice: {
select: deviceSummarySelect,
},
},
orderBy: { createdAt: "desc" },
take: 24,
}),
prisma.toolCallLog.findMany({
where: {
elderDeviceId: { in: elderDeviceIds },
},
include: {
elderDevice: {
select: deviceSummarySelect,
},
},
orderBy: { createdAt: "desc" },
take: 24,
}),
]);
const deviceNameMap = await loadCaregiverDeviceNameMap(caregiverId, elderDeviceIds);
applyCaregiverDeviceNames(usageEvents, deviceNameMap);
applyCaregiverDeviceNames(conversationTurns, deviceNameMap);
applyCaregiverDeviceNames(toolCalls, deviceNameMap);
return {
usageEvents,
conversationTurns,
toolCalls,
};
}
export async function getCaregiverSettingsOverview(caregiverId: string) {
const [deviceCount, pushSubscriptionCount] = await prisma.$transaction([
prisma.deviceBinding.count({
where: { caregiverId },
}),
prisma.pushSubscription.count({
where: { caregiverId },
}),
]);
return {
deviceCount,
pushSubscriptionCount,
};
}