From 20ae33270691f6de212f1ce440a48afae253d5e9 Mon Sep 17 00:00:00 2001 From: feie9456 Date: Wed, 15 Apr 2026 11:07:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=9F=8B=E7=82=B9=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/page.tsx | 204 +++++++ app/api/track/route.ts | 66 +++ app/globals.css | 167 +++++- app/layout.tsx | 43 +- app/page.tsx | 216 +++++-- bun.lock | 302 ++++++++++ components/admin/dashboard-charts.tsx | 108 ++++ lib/analytics/contracts.ts | 115 ++++ lib/analytics/presenter.ts | 87 +++ lib/analytics/server.ts | 536 ++++++++++++++++++ lib/prisma.ts | 25 + lib/site.ts | 9 + lib/utils.ts | 18 + package.json | 15 +- prisma.config.ts | 16 + .../migration.sql | 92 +++ prisma/migrations/migration_lock.toml | 3 + prisma/schema.prisma | 78 +++ 18 files changed, 2013 insertions(+), 87 deletions(-) create mode 100644 app/admin/page.tsx create mode 100644 app/api/track/route.ts create mode 100644 components/admin/dashboard-charts.tsx create mode 100644 lib/analytics/contracts.ts create mode 100644 lib/analytics/presenter.ts create mode 100644 lib/analytics/server.ts create mode 100644 lib/prisma.ts create mode 100644 lib/site.ts create mode 100644 lib/utils.ts create mode 100644 prisma.config.ts create mode 100644 prisma/migrations/20260415023150_add_analytics_tracking/migration.sql create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma diff --git a/app/admin/page.tsx b/app/admin/page.tsx new file mode 100644 index 0000000..b7a6c8d --- /dev/null +++ b/app/admin/page.tsx @@ -0,0 +1,204 @@ +import Link from "next/link"; +import { ArrowUpRight, Clock3, Smartphone, Sparkles } from "lucide-react"; + +import { DashboardCharts } from "@/components/admin/dashboard-charts"; +import { getDashboardData, parseDashboardQuery } from "@/lib/analytics/server"; +import { formatDateTime, formatNumber } from "@/lib/utils"; + +export const dynamic = "force-dynamic"; + +const ranges = [ + { label: "24 小时", value: "1" }, + { label: "7 天", value: "7" }, + { label: "30 天", value: "30" }, + { label: "90 天", value: "90" }, +] as const; + +type AdminPageProps = { + searchParams: Promise<{ + range?: string; + }>; +}; + +export default async function AdminPage({ searchParams }: AdminPageProps) { + const rawParams = await searchParams; + const query = parseDashboardQuery(rawParams); + const data = await getDashboardData(query); + + return ( +
+
+
+
+
+
+
+
+ + Android 陪伴数据台 +
+
+

使用总览

+

+ 最近谁在使用,哪里需要留意,一眼看清。 +

+
+

+ 这里持续汇总老年陪伴助手在 Android 应用中的使用情况,方便你查看哪些功能最常被打开、哪些设备最近还在使用,以及哪里出现了中断或失败。 +

+
+ +
+ + 返回首页 + + +
+ Android 应用 + 数据自动更新 +
+
+
+ +
+
+

时间范围

+
+ {ranges.map((item) => ( + + {item.label} + + ))} +
+
+
+
+ +
+ {data.stats.map((stat) => ( +
+

{stat.label}

+ {formatNumber(stat.value)} +

{stat.hint}

+
+ ))} +
+ + + +
+
+
+
+

常用功能

+

最近更常打开哪里

+
+
+ + 近 {query.days} 天 +
+
+ +
+ {data.topPaths.map((item, index) => ( +
+
+ {index + 1} +
+

功能页面

+

{item.label}

+
+
+ {formatNumber(item.total)} +
+ ))} +
+
+ +
+
+
+

最近活跃设备

+

刚刚还在使用的是谁

+
+
+ +
+ {data.recentSessions.map((session) => ( +
+
+
+ + Android 设备 +
+ {formatDateTime(session.lastSeenAt)} +
+
+

{session.deviceLabel}

+

设备会话 {session.sessionKey.slice(0, 18)}...

+

+ {formatNumber(session.eventCount)} 条事件 + {session.appVersion ? ` · 版本 ${session.appVersion}` : ""} + {session.locale ? ` · ${session.locale}` : ""} +

+
+
+ ))} +
+
+
+ +
+
+
+

最近记录

+

刚刚发生了什么

+
+
+
+ + + + + + + + + + + + {data.recentEvents.map((event) => ( + + + + + + + + ))} + +
时间终端动作页面 / 功能补充说明
{formatDateTime(event.occurredAt)}Android + + {event.name} + +

{event.type}

+
+

{event.pathname ?? "未标记功能"}

+

{event.component ?? "无额外标签"}

+
+

{event.elementText ?? "无额外说明"}

+

设备会话 {event.sessionKey.slice(0, 18)}...

+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/api/track/route.ts b/app/api/track/route.ts new file mode 100644 index 0000000..17f41fe --- /dev/null +++ b/app/api/track/route.ts @@ -0,0 +1,66 @@ +import { ZodError } from "zod"; + +import { ingestAnalyticsBatch } from "@/lib/analytics/server"; +import { TRACK_API_PATH } from "@/lib/site"; + +export const dynamic = "force-dynamic"; + +const responseHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Headers": "content-type, authorization", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + "Cache-Control": "no-store", +}; + +export async function GET() { + return Response.json( + { + ok: true, + endpoint: TRACK_API_PATH, + message: "Analytics ingestion endpoint is ready.", + }, + { headers: responseHeaders }, + ); +} + +export async function OPTIONS() { + return new Response(null, { + status: 204, + headers: responseHeaders, + }); +} + +export async function POST(request: Request) { + try { + const payload = await request.json(); + const result = await ingestAnalyticsBatch(payload, request.headers); + + return Response.json( + { + ok: true, + accepted: result.accepted, + sessionKey: result.sessionKey, + }, + { headers: responseHeaders }, + ); + } catch (error) { + if (error instanceof ZodError) { + return Response.json( + { + ok: false, + error: "INVALID_ANALYTICS_PAYLOAD", + issues: error.issues, + }, + { status: 400, headers: responseHeaders }, + ); + } + + return Response.json( + { + ok: false, + error: error instanceof Error ? error.message : "UNKNOWN_ERROR", + }, + { status: 500, headers: responseHeaders }, + ); + } +} \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index a2dc41e..dd815a5 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,26 +1,165 @@ @import "tailwindcss"; :root { - --background: #ffffff; - --foreground: #171717; + --surface-0: #fff7f0; + --surface-1: rgba(255, 251, 246, 0.84); + --surface-2: #f8e6d7; + --surface-3: #f2dcc7; + --ink-1: #241915; + --ink-2: #5f4a3d; + --ink-3: #9a806d; + --accent-1: #b6522d; + --accent-2: #1d6f68; + --accent-3: #e39657; + --line-soft: rgba(86, 61, 39, 0.12); + --shadow-card: 0 20px 50px rgba(81, 51, 30, 0.12); + --shadow-soft: 0 28px 60px rgba(95, 55, 24, 0.08); } @theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); + --color-background: var(--surface-0); + --color-foreground: var(--ink-1); + --font-sans: var(--font-body); + --font-mono: var(--font-mono); } -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } +* { + box-sizing: border-box; +} + +html { + min-height: 100%; } body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + min-height: 100vh; + background: + radial-gradient(circle at top left, rgba(251, 219, 190, 0.75) 0%, rgba(251, 219, 190, 0) 28%), + radial-gradient(circle at bottom right, rgba(194, 230, 221, 0.65) 0%, rgba(194, 230, 221, 0) 30%), + linear-gradient(180deg, #fffaf5 0%, #fff7f0 32%, #f8efe5 100%); + color: var(--ink-1); + font-family: var(--font-body), "Noto Sans SC", sans-serif; +} + +a { + color: inherit; + text-decoration: none; +} + +::selection { + background: rgba(182, 82, 45, 0.18); +} + +.soft-card { + border: 1px solid var(--line-soft); + border-radius: 32px; + background: var(--surface-1); + box-shadow: var(--shadow-soft); + backdrop-filter: blur(18px); +} + +.hero-orb { + position: absolute; + pointer-events: none; + height: 320px; + width: 320px; + border-radius: 999px; + background: radial-gradient(circle, rgba(230, 150, 87, 0.18) 0%, rgba(230, 150, 87, 0) 72%); +} + +.eyebrow { + margin-bottom: 0.6rem; + color: var(--accent-1); + font-size: 0.82rem; + font-weight: 700; + letter-spacing: 0.24em; + text-transform: uppercase; +} + +.pill-tag { + display: inline-flex; + align-items: center; + gap: 0.45rem; + border-radius: 999px; + border: 1px solid var(--line-soft); + background: rgba(255, 255, 255, 0.75); + padding: 0.55rem 0.9rem; +} + +.stat-card { + border: 1px solid var(--line-soft); + border-radius: 28px; + background: rgba(255, 255, 255, 0.82); + padding: 1.35rem; + box-shadow: var(--shadow-card); +} + +.stat-label { + font-size: 0.86rem; + color: var(--ink-3); +} + +.stat-value { + display: block; + margin-top: 0.7rem; + font-family: var(--font-display), serif; + font-size: clamp(2rem, 4vw, 3rem); + line-height: 1; + color: var(--ink-1); +} + +.stat-hint { + margin-top: 0.85rem; + font-size: 0.95rem; + line-height: 1.65; + color: var(--ink-2); +} + +.filter-pill { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 999px; + border: 1px solid var(--line-soft); + background: rgba(255, 255, 255, 0.72); + padding: 0.7rem 1rem; + color: var(--ink-2); + transition: + transform 140ms ease, + background-color 140ms ease, + border-color 140ms ease; +} + +.filter-pill:hover { + transform: translateY(-1px); + background: rgba(255, 255, 255, 0.96); +} + +.filter-pill-active { + border-color: rgba(182, 82, 45, 0.18); + background: linear-gradient(135deg, rgba(182, 82, 45, 0.14), rgba(182, 82, 45, 0.06)); + color: var(--ink-1); +} + +.index-badge { + display: inline-flex; + height: 2rem; + width: 2rem; + align-items: center; + justify-content: center; + border-radius: 999px; + background: var(--surface-2); + color: var(--accent-1); + font-weight: 700; +} + +.list-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + border-radius: 26px; + border: 1px solid var(--line-soft); + background: rgba(255, 255, 255, 0.8); + padding: 1rem 1.1rem; } diff --git a/app/layout.tsx b/app/layout.tsx index 976eb90..c1d02c0 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,20 +1,43 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import { Fraunces, IBM_Plex_Mono, Noto_Sans_SC } from "next/font/google"; + +import { SITE_NAME, SITE_URL } from "@/lib/site"; + import "./globals.css"; -const geistSans = Geist({ - variable: "--font-geist-sans", +const bodyFont = Noto_Sans_SC({ + variable: "--font-body", subsets: ["latin"], + weight: ["400", "500", "700"], }); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", +const displayFont = Fraunces({ + variable: "--font-display", subsets: ["latin"], + weight: ["500", "700"], +}); + +const monoFont = IBM_Plex_Mono({ + variable: "--font-mono", + subsets: ["latin"], + weight: ["400", "500"], }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + metadataBase: new URL(SITE_URL), + title: { + default: SITE_NAME, + template: `%s | ${SITE_NAME}`, + }, + description: "查看老年陪伴助手 Android 应用的使用情况、关键流程和异常提醒。", + openGraph: { + title: SITE_NAME, + description: "聚焦老年陪伴助手 Android 应用的使用概况与运营观察。", + url: SITE_URL, + siteName: SITE_NAME, + locale: "zh_CN", + type: "website", + }, }; export default function RootLayout({ @@ -24,10 +47,10 @@ export default function RootLayout({ }>) { return ( - {children} + {children} ); } diff --git a/app/page.tsx b/app/page.tsx index 3f36f7c..a5d0a08 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,65 +1,159 @@ -import Image from "next/image"; +import Link from "next/link"; +import { Activity, ArrowRight, Clock3, Smartphone, Sparkles } from "lucide-react"; + +import { getPublicAnalyticsSnapshot } from "@/lib/analytics/server"; +import { ADMIN_PATH } from "@/lib/site"; +import { formatNumber } from "@/lib/utils"; + +export const dynamic = "force-dynamic"; + +const focusCards = [ + { + icon: Smartphone, + title: "看哪些长辈还在持续使用", + description: "活跃设备和最近打开的功能会持续更新,方便判断陪伴助手有没有被真正用起来。", + }, + { + icon: Sparkles, + title: "看陪伴流程是否顺畅", + description: "进入通话、开始互动、创建专属音色这些关键动作,都会按时间留下记录。", + }, + { + icon: Activity, + title: "看哪里需要尽快处理", + description: "当连接、播放、录音或资源准备失败时,后台会留下清晰的异常提醒。", + }, +]; + +export default async function Home() { + const snapshot = await getPublicAnalyticsSnapshot(); -export default function Home() { return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
-
- - Vercel logomark - Deploy Now - - - Documentation - -
-
-
+
+
+
+
+
+
+
+
+
+ 老年陪伴助手 + Android 使用概览 +
+ +
+

陪伴助手总览

+

+ 把每一次陪伴是否顺畅,看得更清楚一些。 +

+

+ 在这里关注老年陪伴助手数字人应用里的真实使用情况,帮助你了解长辈最近是否在使用、常用哪些功能、有没有遇到卡住或失败。 +

+
+ +
+ + 打开管理员面板 + + +
+
+ +
+
+

24 小时互动记录

+ {formatNumber(snapshot.events24h)} +

最近一天里记录到的关键动作

+
+
+

24 小时活跃设备

+ {formatNumber(snapshot.sessions24h)} +

最近一天里仍在使用的 Android 设备

+
+
+

最近最常见动作

+ {snapshot.topAction} +

帮助快速判断大家最近最常用哪项能力

+
+
+
+
+ +
+ {focusCards.map((card) => { + const Icon = card.icon; + return ( +
+
+ +
+

{card.title}

+

{card.description}

+
+ ); + })} +
+ +
+
+

后台重点关注

+

这些关键过程会持续留痕

+
+ {[ + "进入首页、通话页和音色克隆页", + "选择形象、进入陪伴通话", + "开始录音、重录、完成录音", + "创建专属音色和开始体验", + "陪伴通话连接、断开和结束", + "资源准备、播放和摄像头异常", + ].map((item) => ( +
+ {item} +
+ ))} +
+
+ +
+

适合日常巡检

+

打开后台后,先看这三件事

+
+ {[ + { + icon: Clock3, + title: "先看最近一天是不是还有人在使用", + description: "如果活跃设备突然明显减少,就要尽快确认入口、资源和服务是不是出了问题。", + }, + { + icon: Sparkles, + title: "再看最近最常见的动作有没有变化", + description: "高频动作能反映长辈现在更依赖哪项能力,也能判断新功能有没有被接受。", + }, + { + icon: Activity, + title: "最后看异常提醒有没有集中出现", + description: "一旦某类失败突然增多,通常就说明某个陪伴流程需要尽快排查。", + }, + ].map((item) => { + const Icon = item.icon; + return ( +
+
+ +
+

{item.title}

+

{item.description}

+
+ ); + })} +
+
+
+
+
); } diff --git a/bun.lock b/bun.lock index 20711b5..2e79397 100644 --- a/bun.lock +++ b/bun.lock @@ -5,9 +5,17 @@ "": { "name": "digital-human-monitor", "dependencies": { + "@prisma/adapter-pg": "^7.7.0", + "@prisma/client": "^7.7.0", + "clsx": "^2.1.1", + "dotenv": "^17.4.2", + "lucide-react": "^1.8.0", "next": "16.2.3", + "prisma": "^7.7.0", "react": "19.2.4", "react-dom": "19.2.4", + "recharts": "^3.8.1", + "zod": "^4.3.6", }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -60,6 +68,12 @@ "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "@electric-sql/pglite": ["@electric-sql/pglite@0.4.1", "", {}, "sha512-mZ9NzzUSYPOCnxHH1oAHPRzoMFJHY472raDKwXl/+6oPbpdJ7g8LsCN4FSaIIfkiCKHhb3iF/Zqo3NYxaIhU7Q=="], + + "@electric-sql/pglite-socket": ["@electric-sql/pglite-socket@0.1.1", "", { "peerDependencies": { "@electric-sql/pglite": "0.4.1" }, "bin": { "pglite-server": "dist/scripts/server.js" } }, "sha512-p2hoXw3Z3LQHwTeikdZNsFBOvXGqKY2hk51BBw+8NKND8eoH+8LFOtW9Z8CQKmTJ2qqGYu82ipqiyFZOTTXNfw=="], + + "@electric-sql/pglite-tools": ["@electric-sql/pglite-tools@0.3.1", "", { "peerDependencies": { "@electric-sql/pglite": "0.4.1" } }, "sha512-C+T3oivmy9bpQvSxVqXA1UDY8cB9Eb9vZHL9zxWwEUfDixbXv4G3r2LjoTdR33LD8aomR3O9ZXEO3XEwr/cUCA=="], + "@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], "@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], @@ -84,6 +98,8 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="], + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], @@ -152,6 +168,8 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], "@next/env": ["@next/env@16.2.3", "", {}, "sha512-ZWXyj4uNu4GCWQw9cjRxWlbD+33mcDszIo9iQxFnBX3Wmgq9ulaSJcl6VhuWx5pCWqqD+9W6Wfz7N0lM5lYPMA=="], @@ -182,8 +200,58 @@ "@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="], + "@prisma/adapter-pg": ["@prisma/adapter-pg@7.7.0", "", { "dependencies": { "@prisma/driver-adapter-utils": "7.7.0", "@types/pg": "^8.16.0", "pg": "^8.16.3", "postgres-array": "3.0.4" } }, "sha512-q33Ta8sKbgzEpAy0lx45tAq//yMv0qcb+8nj+TCA3P4wiAY+OBFEFk/NDkZncAfHaNJeGo5WJpJdpbL+ijYx8g=="], + + "@prisma/client": ["@prisma/client@7.7.0", "", { "dependencies": { "@prisma/client-runtime-utils": "7.7.0" }, "peerDependencies": { "prisma": "*", "typescript": ">=5.4.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-5Ar4OsZpJ54s21sy5oDNNW9gQtd4NuxCaiM7+JDTOU07D6VvlpLjYzAVCMB1+JzokN+08dAVomlx+b7bhJd3ww=="], + + "@prisma/client-runtime-utils": ["@prisma/client-runtime-utils@7.7.0", "", {}, "sha512-BLyd0UpFYOtyJFTHm7jS9vesHW7P83abibodQMiIofqjBKzDHQ1VAsQkdfvXyYDkPlONPfOTz7/rv3x/+CQqvQ=="], + + "@prisma/config": ["@prisma/config@7.7.0", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.20.0", "empathic": "2.0.0" } }, "sha512-hmPI3tKLO2aP0Y5vugbjcnA9qqlfJndiT6ds4tw28U5hNHLWg+mHJEWAhjsSPgxjtmxhJ/EDIeIlyh+3Us0OPg=="], + + "@prisma/debug": ["@prisma/debug@7.7.0", "", {}, "sha512-12J62XdqCmpiwJHhHdQxZeY3ckVCWIFmcJP8hg5dPTceeiQ0wiojXGFYTluKqFQfu46fRLgb/rLALZMAx3+dTA=="], + + "@prisma/dev": ["@prisma/dev@0.24.3", "", { "dependencies": { "@electric-sql/pglite": "0.4.1", "@electric-sql/pglite-socket": "0.1.1", "@electric-sql/pglite-tools": "0.3.1", "@hono/node-server": "1.19.11", "@prisma/get-platform": "7.2.0", "@prisma/query-plan-executor": "7.2.0", "@prisma/streams-local": "0.1.2", "foreground-child": "3.3.1", "get-port-please": "3.2.0", "hono": "^4.12.8", "http-status-codes": "2.3.0", "pathe": "2.0.3", "proper-lockfile": "4.1.2", "remeda": "2.33.4", "std-env": "3.10.0", "valibot": "1.2.0", "zeptomatch": "2.1.0" } }, "sha512-ffHlQuKXZiaDt9Go0OnCTdJZrHxK0k7omJKNV86/VjpsXu5EIHZLK0T7JSWgvNlJwh56kW9JFu9v0qJciFzepg=="], + + "@prisma/driver-adapter-utils": ["@prisma/driver-adapter-utils@7.7.0", "", { "dependencies": { "@prisma/debug": "7.7.0" } }, "sha512-gZXREeu6mOk7zXfGFJgh86p7Vhj0sXNKp+4Cg1tWYo7V2dfncP2qxS2BiTmbIIha8xPqItkl0WSw38RuSq1HoQ=="], + + "@prisma/engines": ["@prisma/engines@7.7.0", "", { "dependencies": { "@prisma/debug": "7.7.0", "@prisma/engines-version": "7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711", "@prisma/fetch-engine": "7.7.0", "@prisma/get-platform": "7.7.0" } }, "sha512-7fmcbT7HHXBq/b+3h/dO1JI3fd8l8q7erf7xP7pRprh58hmSSnG8mg9K3yjW3h9WaHWUwngVFpSxxxivaitQ2w=="], + + "@prisma/engines-version": ["@prisma/engines-version@7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711", "", {}, "sha512-r51DLcJ8bDRSrBEJF3J4cinoWyGA7rfP2mG6lD90VqIbGNOkbfcLcXalSVjq5Y6brQS3vcjrq4GbyUb1Cb7vkw=="], + + "@prisma/fetch-engine": ["@prisma/fetch-engine@7.7.0", "", { "dependencies": { "@prisma/debug": "7.7.0", "@prisma/engines-version": "7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711", "@prisma/get-platform": "7.7.0" } }, "sha512-TfyzveBQoK4xALzsTpVhB/0KG1N8zOK0ap+RnBMkzGUu3f98fnQ4QtXa2wlKPhsO2X8a3N5ugFQgcKNoHGmDfw=="], + + "@prisma/get-platform": ["@prisma/get-platform@7.2.0", "", { "dependencies": { "@prisma/debug": "7.2.0" } }, "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA=="], + + "@prisma/query-plan-executor": ["@prisma/query-plan-executor@7.2.0", "", {}, "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ=="], + + "@prisma/streams-local": ["@prisma/streams-local@0.1.2", "", { "dependencies": { "ajv": "^8.12.0", "better-result": "^2.7.0", "env-paths": "^3.0.0", "proper-lockfile": "^4.1.2" } }, "sha512-l49yTxKKF2odFxaAXTmwmkBKL3+bVQ1tFOooGifu4xkdb9NMNLxHj27XAhTylWZod8I+ISGM5erU1xcl/oBCtg=="], + + "@prisma/studio-core": ["@prisma/studio-core@0.27.3", "", { "dependencies": { "@radix-ui/react-toggle": "1.1.10", "chart.js": "4.5.1" }, "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-AADjNFPdsrglxHQVTmHFqv6DuKQZ5WY4p5/gVFY017twvNrSwpLJ9lqUbYYxEu2W7nbvVxTZA8deJ8LseNALsw=="], + + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], + + "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], + + "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ=="], + + "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], + + "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], + + "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], + + "@reduxjs/toolkit": ["@reduxjs/toolkit@2.11.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ=="], + "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], + "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], @@ -218,6 +286,24 @@ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], + + "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], + + "@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="], + + "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], + + "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], @@ -226,10 +312,14 @@ "@types/node": ["@types/node@20.19.39", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw=="], + "@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="], + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.2", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.2", "@typescript-eslint/type-utils": "8.58.2", "@typescript-eslint/utils": "8.58.2", "@typescript-eslint/visitor-keys": "8.58.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.2", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.2", "@typescript-eslint/types": "8.58.2", "@typescript-eslint/typescript-estree": "8.58.2", "@typescript-eslint/visitor-keys": "8.58.2", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg=="], @@ -322,6 +412,8 @@ "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], + "axe-core": ["axe-core@4.11.3", "", {}, "sha512-zBQouZixDTbo3jMGqHKyePxYxr1e5W8UdTmBQ7sNtaA9M2bE32daxxPLS/jojhKOHxQ7LWwPjfiwf/fhaJWzlg=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], @@ -330,12 +422,16 @@ "baseline-browser-mapping": ["baseline-browser-mapping@2.10.19", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g=="], + "better-result": ["better-result@2.8.2", "", {}, "sha512-YOf0VSj5nUPI27doTtXF+BBnsiRq3qY7avHqfIWnppxTLGyvkLq1QV2RTxkwoZwJ60ywLfZ0raFF4J/G886i7A=="], + "brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + "c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="], + "call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -348,20 +444,54 @@ "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "chart.js": ["chart.js@4.5.1", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], + "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="], + + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + "damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="], "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], @@ -372,24 +502,42 @@ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="], + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], + + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], + + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], + "dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + "effect": ["effect@3.20.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw=="], + "electron-to-chromium": ["electron-to-chromium@1.5.336", "", {}, "sha512-AbH9q9J455r/nLmdNZes0G0ZKcRX73FicwowalLs6ijwOmCJSRRrLX63lcAlzy9ux3dWK1w1+1nsBJEWN11hcQ=="], "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], + "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], + "env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="], + "es-abstract": ["es-abstract@1.24.2", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="], "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], @@ -406,6 +554,8 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], + "es-toolkit": ["es-toolkit@1.45.1", "", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], @@ -442,6 +592,12 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], + + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + + "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="], @@ -450,6 +606,8 @@ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -466,24 +624,32 @@ "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="], "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], + "generate-function": ["generate-function@2.3.1", "", { "dependencies": { "is-property": "^1.0.2" } }, "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ=="], + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + "get-port-please": ["get-port-please@3.2.0", "", {}, "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A=="], + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], "get-tsconfig": ["get-tsconfig@4.13.8", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-J87BxkLXykmisLQ+KA4x2+O6rVf+PJrtFUO8lGyiRg4lyxJLJ8/v0sRAKdVZQOy6tR6lMRAF1NqzCf9BQijm0w=="], + "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="], @@ -494,6 +660,10 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "grammex": ["grammex@3.1.12", "", {}, "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ=="], + + "graphmatch": ["graphmatch@1.1.1", "", {}, "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg=="], + "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], @@ -512,14 +682,24 @@ "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], + "hono": ["hono@4.12.12", "", {}, "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q=="], + + "http-status-codes": ["http-status-codes@2.3.0", "", {}, "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA=="], + + "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], + "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="], @@ -554,6 +734,8 @@ "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], + "is-property": ["is-property@1.0.2", "", {}, "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g=="], + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="], @@ -632,10 +814,16 @@ "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "lru.min": ["lru.min@1.1.4", "", {}, "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA=="], + + "lucide-react": ["lucide-react@1.8.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], @@ -650,6 +838,10 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "mysql2": ["mysql2@3.15.3", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg=="], + + "named-placeholders": ["named-placeholders@1.1.6", "", { "dependencies": { "lru.min": "^1.1.0" } }, "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w=="], + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="], @@ -660,8 +852,12 @@ "node-exports-info": ["node-exports-info@1.6.0", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="], + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], + "node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="], + "nypm": ["nypm@0.6.5", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], @@ -678,6 +874,8 @@ "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], @@ -694,38 +892,96 @@ "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], + + "pg": ["pg@8.20.0", "", { "dependencies": { "pg-connection-string": "^2.12.0", "pg-pool": "^3.13.0", "pg-protocol": "^1.13.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA=="], + + "pg-cloudflare": ["pg-cloudflare@1.3.0", "", {}, "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ=="], + + "pg-connection-string": ["pg-connection-string@2.12.0", "", {}, "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ=="], + + "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], + + "pg-pool": ["pg-pool@3.13.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA=="], + + "pg-protocol": ["pg-protocol@1.13.0", "", {}, "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w=="], + + "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], + + "pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], "postcss": ["postcss@8.5.9", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw=="], + "postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="], + + "postgres-array": ["postgres-array@3.0.4", "", {}, "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ=="], + + "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], + + "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], + + "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + "prisma": ["prisma@7.7.0", "", { "dependencies": { "@prisma/config": "7.7.0", "@prisma/dev": "0.24.3", "@prisma/engines": "7.7.0", "@prisma/studio-core": "0.27.3", "mysql2": "3.15.3", "postgres": "3.4.7" }, "peerDependencies": { "better-sqlite3": ">=9.0.0", "typescript": ">=5.4.0" }, "optionalPeers": ["better-sqlite3", "typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-HlgwRBt1uEFB9LStHL4HLYDvoi4BNu1rYA0hPG0zCAEyK9SaZBqp7E5Rjpc3Qh8Lex/ye/svoHZ0OWoFNhWxuQ=="], + "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], + "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "recharts": ["recharts@3.8.1", "", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg=="], + + "redux": ["redux@5.0.1", "", {}, "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="], + + "redux-thunk": ["redux-thunk@3.1.0", "", { "peerDependencies": { "redux": "^5.0.0" } }, "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw=="], + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], + "remeda": ["remeda@2.33.4", "", {}, "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="], + "resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], @@ -736,10 +992,14 @@ "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "seq-queue": ["seq-queue@0.0.5", "", {}, "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="], + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], @@ -760,10 +1020,18 @@ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="], + "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], "string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="], @@ -792,6 +1060,10 @@ "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="], + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + + "tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="], + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], @@ -826,6 +1098,12 @@ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + + "valibot": ["valibot@1.2.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg=="], + + "victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], @@ -838,10 +1116,14 @@ "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "zeptomatch": ["zeptomatch@2.1.0", "", { "dependencies": { "grammex": "^3.1.11", "graphmatch": "^1.1.0" } }, "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA=="], + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], @@ -852,6 +1134,16 @@ "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + "@prisma/engines/@prisma/get-platform": ["@prisma/get-platform@7.7.0", "", { "dependencies": { "@prisma/debug": "7.7.0" } }, "sha512-MEUNzvKxvYnJ7kgvd6oNRnMmmiGNS9TYLB2weMeIXplnHdL/UWEGnvavYGnN7KLJ2n0iI4dDAyzSkHI3c7AscQ=="], + + "@prisma/fetch-engine/@prisma/get-platform": ["@prisma/get-platform@7.7.0", "", { "dependencies": { "@prisma/debug": "7.7.0" } }, "sha512-MEUNzvKxvYnJ7kgvd6oNRnMmmiGNS9TYLB2weMeIXplnHdL/UWEGnvavYGnN7KLJ2n0iI4dDAyzSkHI3c7AscQ=="], + + "@prisma/get-platform/@prisma/debug": ["@prisma/debug@7.2.0", "", {}, "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw=="], + + "@prisma/streams-local/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], + + "@reduxjs/toolkit/immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], @@ -872,6 +1164,8 @@ "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "c12/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], "eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], @@ -886,8 +1180,16 @@ "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + "nypm/citty": ["citty@0.2.2", "", {}, "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w=="], + + "pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], + + "proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "@prisma/streams-local/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], diff --git a/components/admin/dashboard-charts.tsx b/components/admin/dashboard-charts.tsx new file mode 100644 index 0000000..783a26c --- /dev/null +++ b/components/admin/dashboard-charts.tsx @@ -0,0 +1,108 @@ +"use client"; + +import { + Area, + AreaChart, + Bar, + BarChart, + CartesianGrid, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; + +import type { DashboardTopItem, DashboardTrendPoint } from "@/lib/analytics/contracts"; +import { formatNumber } from "@/lib/utils"; + +function ChartTooltip({ active, payload, label }: { active?: boolean; payload?: Array<{ value: number; name?: string }>; label?: string }) { + if (!active || !payload?.length) { + return null; + } + + return ( +
+ {label ?

{label}

: null} + {payload.map((item) => ( +

+ {item.name ?? "总量"}: {formatNumber(item.value)} +

+ ))} +
+ ); +} + +export function DashboardCharts({ + activityTrend, + topEvents, +}: { + activityTrend: DashboardTrendPoint[]; + topEvents: DashboardTopItem[]; +}) { + return ( +
+
+
+
+

陪伴趋势

+

最近互动走势

+
+

看什么时候更活跃,什么时候需要多留意

+
+ +
+ + + + + + + + + + + + } /> + + + +
+
+ +
+
+
+

高频动作

+

最常发生的陪伴操作

+
+

最近大家更常用哪项能力,这里最直观

+
+
+ + + + + + } /> + + + +
+
+
+ ); +} \ No newline at end of file diff --git a/lib/analytics/contracts.ts b/lib/analytics/contracts.ts new file mode 100644 index 0000000..3100933 --- /dev/null +++ b/lib/analytics/contracts.ts @@ -0,0 +1,115 @@ +export const ANALYTICS_RANGE_OPTIONS = [1, 7, 30, 90] as const; +export const TRACK_FLUSH_INTERVAL_MS = 5000; +export const TRACK_BATCH_SIZE = 12; + +export type AnalyticsPlatform = "WEB" | "ANDROID"; + +export type JsonPrimitive = string | number | boolean | null; +export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }; + +export interface AnalyticsSessionPayload { + sessionKey: string; + platform: AnalyticsPlatform; + domain?: string; + channel?: string; + visitorId?: string; + userId?: string; + locale?: string; + timezone?: string; + referrer?: string; + userAgent?: string; + ipAddress?: string; + appVersion?: string; + browser?: string; + browserVersion?: string; + deviceType?: string; + deviceBrand?: string; + deviceModel?: string; + osName?: string; + osVersion?: string; + screenWidth?: number; + screenHeight?: number; +} + +export interface AnalyticsEventPayload { + name: string; + type: string; + pageUrl?: string; + pathname?: string; + title?: string; + referrer?: string; + elementTag?: string; + elementText?: string; + elementId?: string; + elementRole?: string; + component?: string; + value?: string; + durationMs?: number; + sequence?: number; + occurredAt?: string; + metadata?: JsonValue; +} + +export interface AnalyticsBatchPayload { + session: AnalyticsSessionPayload; + events: AnalyticsEventPayload[]; +} + +export interface DashboardQuery { + days: number; +} + +export interface DashboardStat { + label: string; + value: number; + hint: string; +} + +export interface DashboardTrendPoint { + bucket: string; + total: number; +} + +export interface DashboardTopItem { + label: string; + total: number; + detail?: string | null; +} + +export interface DashboardRecentEvent { + id: string; + sessionKey: string; + platform: AnalyticsPlatform; + name: string; + type: string; + pathname: string | null; + component: string | null; + elementText: string | null; + occurredAt: string; + metadata: JsonValue | null; +} + +export interface DashboardRecentSession { + sessionKey: string; + platform: AnalyticsPlatform; + eventCount: number; + locale: string | null; + appVersion: string | null; + deviceLabel: string; + lastSeenAt: string; +} + +export interface DashboardData { + stats: DashboardStat[]; + activityTrend: DashboardTrendPoint[]; + topEvents: DashboardTopItem[]; + topPaths: DashboardTopItem[]; + recentEvents: DashboardRecentEvent[]; + recentSessions: DashboardRecentSession[]; +} + +export interface PublicAnalyticsSnapshot { + events24h: number; + sessions24h: number; + topAction: string; +} \ No newline at end of file diff --git a/lib/analytics/presenter.ts b/lib/analytics/presenter.ts new file mode 100644 index 0000000..57c6a9c --- /dev/null +++ b/lib/analytics/presenter.ts @@ -0,0 +1,87 @@ +const EVENT_LABELS: Record = { + screen_view: "打开页面", + voice_clone_entry_tap: "进入音色克隆", + avatar_selected: "选择数字人形象", + call_page_opened: "进入陪伴通话", + base_config_download_started: "开始准备基础资源", + base_config_download_completed: "基础资源准备完成", + base_config_download_failed: "基础资源准备失败", + avatar_download_started: "开始准备数字人资源", + avatar_download_completed: "数字人资源准备完成", + avatar_download_failed: "数字人资源准备失败", + clone_avatar_selected: "选择克隆形象", + voice_sample_discarded: "删除录音样本", + voice_rerecord_requested: "重新录音", + voice_record_started: "开始录音", + voice_record_start_failed: "录音启动失败", + voice_record_too_short: "录音时长过短", + voice_record_finished: "录音完成", + voice_record_stop_failed: "录音结束失败", + voice_clone_create_started: "开始创建专属音色", + voice_clone_create_succeeded: "专属音色创建成功", + voice_clone_create_failed: "专属音色创建失败", + voice_clone_chat_started: "开始体验专属音色", + call_activity_opened: "打开通话页面", + ai_conversation_unavailable: "陪伴通话暂不可用", + clone_voice_unavailable: "专属音色暂不可用", + ai_conversation_connecting: "陪伴通话连接中", + ai_conversation_connected: "陪伴通话已连接", + ai_conversation_disconnected: "陪伴通话已断开", + ai_conversation_error: "陪伴通话异常", + user_transcript_received: "收到用户语音文本", + assistant_transcript_completed: "陪伴回复已生成", + assistant_response_done: "陪伴回复结束", + ai_conversation_stopped: "陪伴通话已结束", + clone_tts_started: "开始播放专属音色", + clone_tts_finished: "专属音色播放完成", + clone_tts_error: "专属音色播放失败", + camera_enabled: "打开摄像头", + camera_disabled: "关闭摄像头", + camera_capture_error: "摄像头采集失败", +}; + +const PATH_LABELS: Record = { + "/android/main": "首页", + "/android/call": "陪伴通话", + "/android/voice-clone-setup": "音色克隆", + "(未标记页面)": "未标记功能", +}; + +function humanizeFallback(value: string) { + const normalized = value.trim().replace(/[_-]+/g, " "); + return normalized || "未命名记录"; +} + +export function describeAnalyticsEventName(name: string) { + return EVENT_LABELS[name] ?? humanizeFallback(name); +} + +export function describeAnalyticsPath(pathname: string | null | undefined) { + if (!pathname) { + return "未标记功能"; + } + + if (PATH_LABELS[pathname]) { + return PATH_LABELS[pathname]; + } + + if (pathname.startsWith("/android/")) { + const label = pathname.replace("/android/", "").replace(/-/g, " ").trim(); + return label ? `Android 功能 ${label}` : "Android 功能"; + } + + return pathname; +} + +export function describeAnalyticsType(type: string) { + switch (type.toLowerCase()) { + case "navigation": + return "页面浏览"; + case "interaction": + return "操作记录"; + case "error": + return "异常提醒"; + default: + return humanizeFallback(type); + } +} \ No newline at end of file diff --git a/lib/analytics/server.ts b/lib/analytics/server.ts new file mode 100644 index 0000000..813676a --- /dev/null +++ b/lib/analytics/server.ts @@ -0,0 +1,536 @@ +import "server-only"; + +import { z } from "zod"; + +import type { + AnalyticsPlatform, + AnalyticsBatchPayload, + DashboardData, + DashboardQuery, + JsonValue, + PublicAnalyticsSnapshot, +} from "@/lib/analytics/contracts"; +import { ANALYTICS_RANGE_OPTIONS } from "@/lib/analytics/contracts"; +import { + describeAnalyticsEventName, + describeAnalyticsPath, + describeAnalyticsType, +} from "@/lib/analytics/presenter"; +import { prisma } from "@/lib/prisma"; +import { SITE_DOMAIN } from "@/lib/site"; + +const nullableStringField = (max: number) => z.string().trim().max(max).nullish(); + +const jsonValueSchema: z.ZodType = z.lazy(() => + z.union([ + z.string(), + z.number().finite(), + z.boolean(), + z.null(), + z.array(jsonValueSchema), + z.record(z.string(), jsonValueSchema), + ]), +); + +const analyticsSessionSchema = z.object({ + sessionKey: z.string().trim().min(8).max(120), + platform: z.literal("ANDROID"), + domain: nullableStringField(255), + channel: nullableStringField(80), + visitorId: nullableStringField(120), + userId: nullableStringField(120), + locale: nullableStringField(40), + timezone: nullableStringField(80), + referrer: nullableStringField(500), + userAgent: nullableStringField(500), + ipAddress: nullableStringField(64), + appVersion: nullableStringField(40), + browser: nullableStringField(80), + browserVersion: nullableStringField(80), + deviceType: nullableStringField(40), + deviceBrand: nullableStringField(80), + deviceModel: nullableStringField(120), + osName: nullableStringField(80), + osVersion: nullableStringField(80), + screenWidth: z.number().int().positive().max(10000).nullish(), + screenHeight: z.number().int().positive().max(10000).nullish(), +}); + +const analyticsEventSchema = z.object({ + name: z.string().trim().min(1).max(120), + type: z.string().trim().min(1).max(80), + pageUrl: nullableStringField(500), + pathname: nullableStringField(255), + title: nullableStringField(255), + referrer: nullableStringField(500), + elementTag: nullableStringField(80), + elementText: nullableStringField(240), + elementId: nullableStringField(120), + elementRole: nullableStringField(80), + component: nullableStringField(120), + value: nullableStringField(240), + durationMs: z.number().int().nonnegative().max(2_147_483_647).nullish(), + sequence: z.number().int().nonnegative().max(1_000_000).nullish(), + occurredAt: z.string().datetime().optional(), + metadata: jsonValueSchema.nullish(), +}); + +const analyticsBatchSchema = z.object({ + session: analyticsSessionSchema, + events: z.array(analyticsEventSchema).min(1).max(100), +}); + +type ValidatedBatch = z.infer; + +function truncate(value: string | null | undefined, max: number) { + if (!value) { + return undefined; + } + + const trimmed = value.trim(); + if (!trimmed) { + return undefined; + } + + return trimmed.length > max ? trimmed.slice(0, max) : trimmed; +} + +function readClientIp(requestHeaders: Headers) { + const forwarded = requestHeaders.get("x-forwarded-for"); + if (forwarded) { + return truncate(forwarded.split(",")[0] ?? undefined, 64); + } + + return truncate(requestHeaders.get("x-real-ip"), 64); +} + +function detectBrowser(userAgent: string | undefined) { + if (!userAgent) { + return undefined; + } + + if (/edg\//i.test(userAgent)) { + return "Edge"; + } + if (/chrome\//i.test(userAgent)) { + return "Chrome"; + } + if (/safari\//i.test(userAgent) && !/chrome\//i.test(userAgent)) { + return "Safari"; + } + if (/firefox\//i.test(userAgent)) { + return "Firefox"; + } + if (/okhttp/i.test(userAgent)) { + return "OkHttp"; + } + + return "Unknown"; +} + +function detectOs(userAgent: string | undefined) { + if (!userAgent) { + return { osName: undefined, osVersion: undefined, deviceType: undefined }; + } + + const androidMatch = userAgent.match(/Android\s([\d.]+)/i); + if (androidMatch) { + return { osName: "Android", osVersion: androidMatch[1], deviceType: "mobile" }; + } + + const iosMatch = userAgent.match(/OS\s([\d_]+)/i); + if (iosMatch) { + return { osName: "iOS", osVersion: iosMatch[1]?.replaceAll("_", "."), deviceType: "mobile" }; + } + + if (/Windows/i.test(userAgent)) { + return { osName: "Windows", osVersion: undefined, deviceType: "desktop" }; + } + if (/Mac OS X/i.test(userAgent)) { + return { osName: "macOS", osVersion: undefined, deviceType: "desktop" }; + } + if (/Linux/i.test(userAgent)) { + return { osName: "Linux", osVersion: undefined, deviceType: "desktop" }; + } + + return { osName: undefined, osVersion: undefined, deviceType: undefined }; +} + +function buildRangeFilter(columnName: string, rangeStart: Date) { + return `"${columnName}" >= '${rangeStart.toISOString()}' AND "platform" = 'ANDROID'`; +} + +function countResult(rows: Array<{ total: bigint | number | string | null | undefined }>) { + const total = rows[0]?.total; + return Number(total ?? 0); +} + +function formatBucket(bucket: Date, days: number) { + return new Intl.DateTimeFormat("zh-CN", { + month: "2-digit", + day: "2-digit", + hour: days <= 2 ? "2-digit" : undefined, + }).format(bucket); +} + +function buildDeviceLabel(session: { + deviceBrand: string | null; + deviceModel: string | null; + browser: string | null; + osName: string | null; +}) { + const device = [session.deviceBrand, session.deviceModel].filter(Boolean).join(" ").trim(); + if (device) { + return device; + } + + const browser = [session.browser, session.osName].filter(Boolean).join(" / ").trim(); + return browser || "未标记设备"; +} + +export function parseDashboardQuery(raw: { range?: string }): DashboardQuery { + const parsedDays = Number(raw.range ?? 7); + const days = ANALYTICS_RANGE_OPTIONS.includes(parsedDays as (typeof ANALYTICS_RANGE_OPTIONS)[number]) + ? parsedDays + : 7; + + return { days }; +} + +export function validateAnalyticsPayload(payload: unknown) { + return analyticsBatchSchema.parse(payload); +} + +export async function ingestAnalyticsBatch(payload: unknown, requestHeaders: Headers) { + const validated = validateAnalyticsPayload(payload); + return saveAnalyticsBatch(validated, requestHeaders); +} + +async function saveAnalyticsBatch(payload: ValidatedBatch, requestHeaders: Headers) { + const now = new Date(); + const requestDomain = truncate( + requestHeaders.get("x-forwarded-host") ?? requestHeaders.get("host") ?? SITE_DOMAIN, + 255, + ); + const requestUserAgent = truncate(requestHeaders.get("user-agent"), 500); + const ipAddress = payload.session.ipAddress ?? readClientIp(requestHeaders); + const userAgent = payload.session.userAgent ?? requestUserAgent; + const osGuess = detectOs(userAgent ?? undefined); + const browser = payload.session.browser ?? detectBrowser(userAgent ?? undefined); + + const sessionData = { + sessionKey: payload.session.sessionKey, + platform: payload.session.platform, + domain: truncate(payload.session.domain ?? requestDomain, 255), + channel: truncate(payload.session.channel ?? undefined, 80), + visitorId: truncate(payload.session.visitorId ?? undefined, 120), + userId: truncate(payload.session.userId ?? undefined, 120), + locale: truncate(payload.session.locale ?? undefined, 40), + timezone: truncate(payload.session.timezone ?? undefined, 80), + referrer: truncate(payload.session.referrer ?? undefined, 500), + userAgent, + ipAddress, + appVersion: truncate(payload.session.appVersion ?? undefined, 40), + browser: truncate(browser ?? undefined, 80), + browserVersion: truncate(payload.session.browserVersion ?? undefined, 80), + deviceType: truncate(payload.session.deviceType ?? osGuess.deviceType ?? undefined, 40), + deviceBrand: truncate(payload.session.deviceBrand ?? undefined, 80), + deviceModel: truncate(payload.session.deviceModel ?? undefined, 120), + osName: truncate(payload.session.osName ?? osGuess.osName ?? undefined, 80), + osVersion: truncate(payload.session.osVersion ?? osGuess.osVersion ?? undefined, 80), + screenWidth: payload.session.screenWidth ?? undefined, + screenHeight: payload.session.screenHeight ?? undefined, + lastSeenAt: now, + }; + + const events = payload.events.map((event) => ({ + sessionKey: payload.session.sessionKey, + platform: payload.session.platform, + name: truncate(event.name, 120) ?? "unknown", + type: truncate(event.type, 80) ?? "custom", + pageUrl: truncate(event.pageUrl ?? undefined, 500), + pathname: truncate(event.pathname ?? undefined, 255), + title: truncate(event.title ?? undefined, 255), + referrer: truncate(event.referrer ?? payload.session.referrer ?? undefined, 500), + elementTag: truncate(event.elementTag ?? undefined, 80), + elementText: truncate(event.elementText ?? undefined, 240), + elementId: truncate(event.elementId ?? undefined, 120), + elementRole: truncate(event.elementRole ?? undefined, 80), + component: truncate(event.component ?? undefined, 120), + value: truncate(event.value ?? undefined, 240), + durationMs: event.durationMs ?? undefined, + sequence: event.sequence ?? undefined, + metadata: event.metadata == null ? undefined : event.metadata, + occurredAt: event.occurredAt ? new Date(event.occurredAt) : now, + })); + + const session = await prisma.$transaction(async (tx: { + analyticsSession: typeof prisma.analyticsSession; + analyticsEvent: typeof prisma.analyticsEvent; + }) => { + const activeSession = await tx.analyticsSession.upsert({ + where: { sessionKey: payload.session.sessionKey }, + update: { + ...sessionData, + eventCount: { increment: events.length }, + }, + create: { + ...sessionData, + startedAt: now, + eventCount: events.length, + }, + select: { id: true, sessionKey: true }, + }); + + await tx.analyticsEvent.createMany({ + data: events.map((event) => ({ + ...event, + sessionId: activeSession.id, + })), + }); + + return activeSession; + }); + + return { + accepted: events.length, + sessionKey: session.sessionKey, + }; +} + +export async function getDashboardData(query: DashboardQuery): Promise { + const now = new Date(); + const rangeStart = new Date(now.getTime() - query.days * 24 * 60 * 60 * 1000); + const sessionFilter = buildRangeFilter("lastSeenAt", rangeStart); + const eventFilter = buildRangeFilter("occurredAt", rangeStart); + const sessionWhere = { + lastSeenAt: { gte: rangeStart }, + platform: "ANDROID" as const, + }; + const eventWhere = { + occurredAt: { gte: rangeStart }, + platform: "ANDROID" as const, + }; + + const [ + sessionCountRows, + visitorCountRows, + eventCountRows, + errorCountRows, + activityTrendRows, + topEventRows, + topPathRows, + recentSessions, + recentEvents, + ] = await Promise.all([ + prisma.$queryRawUnsafe>(` + SELECT COUNT(*)::bigint AS total + FROM "AnalyticsSession" + WHERE ${sessionFilter} + `), + prisma.$queryRawUnsafe>(` + SELECT COUNT(DISTINCT COALESCE(NULLIF("visitorId", ''), "sessionKey"))::bigint AS total + FROM "AnalyticsSession" + WHERE ${sessionFilter} + `), + prisma.$queryRawUnsafe>(` + SELECT COUNT(*)::bigint AS total + FROM "AnalyticsEvent" + WHERE ${eventFilter} + `), + prisma.$queryRawUnsafe>(` + SELECT COUNT(*)::bigint AS total + FROM "AnalyticsEvent" + WHERE ${eventFilter} + AND ( + lower("type") = 'error' + OR lower("name") LIKE '%error%' + OR lower("name") LIKE '%fail%' + ) + `), + prisma.$queryRawUnsafe>(` + SELECT date_trunc('${query.days <= 2 ? "hour" : "day"}', "occurredAt") AS bucket, + COUNT(*)::bigint AS total + FROM "AnalyticsEvent" + WHERE ${eventFilter} + GROUP BY 1 + ORDER BY 1 ASC + `), + prisma.$queryRawUnsafe>(` + SELECT "name" AS label, + MIN("type") AS detail, + COUNT(*)::bigint AS total + FROM "AnalyticsEvent" + WHERE ${eventFilter} + GROUP BY "name" + ORDER BY total DESC + LIMIT 8 + `), + prisma.$queryRawUnsafe>(` + SELECT COALESCE(NULLIF("pathname", ''), '(未标记页面)') AS label, + COUNT(*)::bigint AS total + FROM "AnalyticsEvent" + WHERE ${eventFilter} + GROUP BY 1 + ORDER BY total DESC + LIMIT 8 + `), + prisma.analyticsSession.findMany({ + where: sessionWhere, + orderBy: { lastSeenAt: "desc" }, + take: 12, + select: { + sessionKey: true, + platform: true, + eventCount: true, + locale: true, + appVersion: true, + deviceBrand: true, + deviceModel: true, + browser: true, + osName: true, + lastSeenAt: true, + }, + }), + prisma.analyticsEvent.findMany({ + where: eventWhere, + orderBy: { occurredAt: "desc" }, + take: 18, + select: { + id: true, + sessionKey: true, + platform: true, + name: true, + type: true, + pathname: true, + component: true, + elementText: true, + occurredAt: true, + metadata: true, + }, + }), + ]); + + const sessionCount = countResult(sessionCountRows); + const uniqueVisitors = countResult(visitorCountRows); + const eventCount = countResult(eventCountRows); + const errorCount = countResult(errorCountRows); + + return { + stats: [ + { + label: "活跃设备", + value: sessionCount, + hint: `${query.days} 天内仍在上报数据的 Android 设备`, + }, + { + label: "活跃使用者", + value: uniqueVisitors, + hint: "按安装标识或会话编号去重后的实际使用主体", + }, + { + label: "互动记录", + value: eventCount, + hint: sessionCount > 0 ? `平均每台设备 ${(eventCount / sessionCount).toFixed(1)} 条` : "当前还没有数据", + }, + { + label: "异常提醒", + value: errorCount, + hint: "连接、播放、录音和资源准备失败都会在这里体现", + }, + ], + activityTrend: activityTrendRows.map((row: { bucket: Date; total: bigint | number | string }) => ({ + bucket: formatBucket(row.bucket, query.days), + total: Number(row.total), + })), + topEvents: topEventRows.map((row: { label: string; detail: string | null; total: bigint | number | string }) => ({ + label: describeAnalyticsEventName(row.label), + detail: row.detail ? describeAnalyticsType(row.detail) : row.detail, + total: Number(row.total), + })), + topPaths: topPathRows.map((row: { label: string; total: bigint | number | string }) => ({ + label: describeAnalyticsPath(row.label), + total: Number(row.total), + })), + recentEvents: recentEvents.map((event: { + id: string; + sessionKey: string; + platform: AnalyticsPlatform; + name: string; + type: string; + pathname: string | null; + component: string | null; + elementText: string | null; + occurredAt: Date; + metadata: unknown; + }) => ({ + id: event.id, + sessionKey: event.sessionKey, + platform: event.platform, + name: describeAnalyticsEventName(event.name), + type: describeAnalyticsType(event.type), + pathname: describeAnalyticsPath(event.pathname), + component: event.component, + elementText: event.elementText, + occurredAt: event.occurredAt.toISOString(), + metadata: (event.metadata ?? null) as DashboardData["recentEvents"][number]["metadata"], + })), + recentSessions: recentSessions.map((session: { + sessionKey: string; + platform: AnalyticsPlatform; + eventCount: number; + locale: string | null; + appVersion: string | null; + deviceBrand: string | null; + deviceModel: string | null; + browser: string | null; + osName: string | null; + lastSeenAt: Date; + }) => ({ + sessionKey: session.sessionKey, + platform: session.platform, + eventCount: session.eventCount, + locale: session.locale, + appVersion: session.appVersion, + deviceLabel: buildDeviceLabel(session), + lastSeenAt: session.lastSeenAt.toISOString(), + })), + }; +} + +export async function getPublicAnalyticsSnapshot(): Promise { + const rangeStart = new Date(Date.now() - 24 * 60 * 60 * 1000); + const eventFilter = buildRangeFilter("occurredAt", rangeStart); + const sessionFilter = buildRangeFilter("lastSeenAt", rangeStart); + const [eventRows, sessionRows, topActionRows] = await Promise.all([ + prisma.$queryRawUnsafe>(` + SELECT COUNT(*)::bigint AS total + FROM "AnalyticsEvent" + WHERE ${eventFilter} + `), + prisma.$queryRawUnsafe>(` + SELECT COUNT(*)::bigint AS total + FROM "AnalyticsSession" + WHERE ${sessionFilter} + `), + prisma.$queryRawUnsafe>(` + SELECT "name" AS label, COUNT(*)::bigint AS total + FROM "AnalyticsEvent" + WHERE ${eventFilter} + GROUP BY 1 + ORDER BY total DESC + LIMIT 1 + `), + ]); + + const events24h = countResult(eventRows); + const sessions24h = countResult(sessionRows); + + return { + events24h, + sessions24h, + topAction: topActionRows[0] ? describeAnalyticsEventName(topActionRows[0].label) : "等待第一条记录", + }; +} + +export type AnalyticsPayload = AnalyticsBatchPayload; \ No newline at end of file diff --git a/lib/prisma.ts b/lib/prisma.ts new file mode 100644 index 0000000..451ae55 --- /dev/null +++ b/lib/prisma.ts @@ -0,0 +1,25 @@ +import { PrismaPg } from "@prisma/adapter-pg"; +import { PrismaClient } from "@prisma/client"; + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClient | undefined; +}; + +const connectionString = process.env.DATABASE_URL; + +if (!connectionString) { + throw new Error("DATABASE_URL is not configured."); +} + +const adapter = new PrismaPg({ connectionString }); + +export const prisma = + globalForPrisma.prisma ?? + new PrismaClient({ + adapter, + log: process.env.NODE_ENV === "development" ? ["warn", "error"] : ["error"], + }); + +if (process.env.NODE_ENV !== "production") { + globalForPrisma.prisma = prisma; +} \ No newline at end of file diff --git a/lib/site.ts b/lib/site.ts new file mode 100644 index 0000000..8edf0fb --- /dev/null +++ b/lib/site.ts @@ -0,0 +1,9 @@ +export const SITE_NAME = "老年陪伴助手管理台"; +export const SITE_DOMAIN = "digital-human.xn--876a.net"; +export const SITE_URL = `https://${SITE_DOMAIN}`; +export const TRACK_API_PATH = "/api/track"; +export const ADMIN_PATH = "/admin"; + +export function toAbsoluteUrl(pathname: string) { + return new URL(pathname, SITE_URL).toString(); +} \ No newline at end of file diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..d8eed9a --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,18 @@ +import { clsx, type ClassValue } from "clsx"; + +export function cn(...inputs: ClassValue[]) { + return clsx(inputs); +} + +export function formatNumber(value: number) { + return new Intl.NumberFormat("zh-CN").format(value); +} + +export function formatDateTime(value: string | Date) { + return new Intl.DateTimeFormat("zh-CN", { + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + }).format(new Date(value)); +} \ No newline at end of file diff --git a/package.json b/package.json index ec31fc6..d1cbe94 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,23 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "prisma:generate": "prisma generate", + "prisma:migrate": "prisma migrate dev", + "prisma:studio": "prisma studio" }, "dependencies": { + "@prisma/adapter-pg": "^7.7.0", + "@prisma/client": "^7.7.0", + "clsx": "^2.1.1", + "dotenv": "^17.4.2", + "lucide-react": "^1.8.0", "next": "16.2.3", + "prisma": "^7.7.0", "react": "19.2.4", - "react-dom": "19.2.4" + "react-dom": "19.2.4", + "recharts": "^3.8.1", + "zod": "^4.3.6" }, "devDependencies": { "@tailwindcss/postcss": "^4", diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 0000000..19641ad --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,16 @@ +import "dotenv/config"; +import { defineConfig, env } from "prisma/config"; + +type Env = { + DATABASE_URL: string; +}; + +export default defineConfig({ + schema: "prisma/schema.prisma", + migrations: { + path: "prisma/migrations", + }, + datasource: { + url: env("DATABASE_URL"), + }, +}); \ No newline at end of file diff --git a/prisma/migrations/20260415023150_add_analytics_tracking/migration.sql b/prisma/migrations/20260415023150_add_analytics_tracking/migration.sql new file mode 100644 index 0000000..c779a3d --- /dev/null +++ b/prisma/migrations/20260415023150_add_analytics_tracking/migration.sql @@ -0,0 +1,92 @@ +-- CreateEnum +CREATE TYPE "AnalyticsPlatform" AS ENUM ('WEB', 'ANDROID'); + +-- CreateTable +CREATE TABLE "AnalyticsSession" ( + "id" TEXT NOT NULL, + "sessionKey" TEXT NOT NULL, + "platform" "AnalyticsPlatform" NOT NULL, + "domain" TEXT, + "channel" TEXT, + "visitorId" TEXT, + "userId" TEXT, + "locale" TEXT, + "timezone" TEXT, + "referrer" TEXT, + "userAgent" TEXT, + "ipAddress" TEXT, + "appVersion" TEXT, + "browser" TEXT, + "browserVersion" TEXT, + "deviceType" TEXT, + "deviceBrand" TEXT, + "deviceModel" TEXT, + "osName" TEXT, + "osVersion" TEXT, + "screenWidth" INTEGER, + "screenHeight" INTEGER, + "eventCount" INTEGER NOT NULL DEFAULT 0, + "startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "lastSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "AnalyticsSession_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AnalyticsEvent" ( + "id" TEXT NOT NULL, + "sessionId" TEXT NOT NULL, + "sessionKey" TEXT NOT NULL, + "platform" "AnalyticsPlatform" NOT NULL, + "name" TEXT NOT NULL, + "type" TEXT NOT NULL, + "pageUrl" TEXT, + "pathname" TEXT, + "title" TEXT, + "referrer" TEXT, + "elementTag" TEXT, + "elementText" TEXT, + "elementId" TEXT, + "elementRole" TEXT, + "component" TEXT, + "value" TEXT, + "durationMs" INTEGER, + "sequence" INTEGER, + "metadata" JSONB, + "occurredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "AnalyticsEvent_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "AnalyticsSession_sessionKey_key" ON "AnalyticsSession"("sessionKey"); + +-- CreateIndex +CREATE INDEX "AnalyticsSession_platform_lastSeenAt_idx" ON "AnalyticsSession"("platform", "lastSeenAt" DESC); + +-- CreateIndex +CREATE INDEX "AnalyticsSession_visitorId_lastSeenAt_idx" ON "AnalyticsSession"("visitorId", "lastSeenAt" DESC); + +-- CreateIndex +CREATE INDEX "AnalyticsEvent_occurredAt_idx" ON "AnalyticsEvent"("occurredAt" DESC); + +-- CreateIndex +CREATE INDEX "AnalyticsEvent_platform_occurredAt_idx" ON "AnalyticsEvent"("platform", "occurredAt" DESC); + +-- CreateIndex +CREATE INDEX "AnalyticsEvent_name_occurredAt_idx" ON "AnalyticsEvent"("name", "occurredAt" DESC); + +-- CreateIndex +CREATE INDEX "AnalyticsEvent_type_occurredAt_idx" ON "AnalyticsEvent"("type", "occurredAt" DESC); + +-- CreateIndex +CREATE INDEX "AnalyticsEvent_pathname_occurredAt_idx" ON "AnalyticsEvent"("pathname", "occurredAt" DESC); + +-- CreateIndex +CREATE INDEX "AnalyticsEvent_sessionKey_occurredAt_idx" ON "AnalyticsEvent"("sessionKey", "occurredAt" DESC); + +-- AddForeignKey +ALTER TABLE "AnalyticsEvent" ADD CONSTRAINT "AnalyticsEvent_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "AnalyticsSession"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..192cc22 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,78 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" +} + +enum AnalyticsPlatform { + WEB + ANDROID +} + +model AnalyticsSession { + id String @id @default(cuid()) + sessionKey String @unique + platform AnalyticsPlatform + domain String? + channel String? + visitorId String? + userId String? + locale String? + timezone String? + referrer String? + userAgent String? + ipAddress String? + appVersion String? + browser String? + browserVersion String? + deviceType String? + deviceBrand String? + deviceModel String? + osName String? + osVersion String? + screenWidth Int? + screenHeight Int? + eventCount Int @default(0) + startedAt DateTime @default(now()) + lastSeenAt DateTime @default(now()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + events AnalyticsEvent[] + + @@index([platform, lastSeenAt(sort: Desc)]) + @@index([visitorId, lastSeenAt(sort: Desc)]) +} + +model AnalyticsEvent { + id String @id @default(cuid()) + sessionId String + sessionKey String + platform AnalyticsPlatform + name String + type String + pageUrl String? + pathname String? + title String? + referrer String? + elementTag String? + elementText String? + elementId String? + elementRole String? + component String? + value String? + durationMs Int? + sequence Int? + metadata Json? + occurredAt DateTime @default(now()) + createdAt DateTime @default(now()) + session AnalyticsSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) + + @@index([occurredAt(sort: Desc)]) + @@index([platform, occurredAt(sort: Desc)]) + @@index([name, occurredAt(sort: Desc)]) + @@index([type, occurredAt(sort: Desc)]) + @@index([pathname, occurredAt(sort: Desc)]) + @@index([sessionKey, occurredAt(sort: Desc)]) +} \ No newline at end of file