diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 20fe874..0e3001a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,14 +45,21 @@ jobs: - name: Generate release notes id: notes - uses: github/copilot-release-notes@main - with: - base-ref: ${{ steps.prev.outputs.tag }} - head-ref: ${{ steps.tag.outputs.current }} - model: gpt-4.1 - instructions: .github/release-notes-instructions.md env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_TOKEN: ${{ github.token }} + PREVIOUS_TAG: ${{ steps.prev.outputs.tag }} + RELEASE_TAG: ${{ steps.tag.outputs.current }} + run: | + args=(--field "tag_name=$RELEASE_TAG") + if [[ "$PREVIOUS_TAG" == v* ]]; then + args+=(--field "previous_tag_name=$PREVIOUS_TAG") + fi + notes_json=$(gh api "repos/${{ github.repository }}/releases/generate-notes" "${args[@]}") + { + echo "release-notes<> "$GITHUB_OUTPUT" - name: Create draft release uses: softprops/action-gh-release@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 84bf0b6..780be99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # 更新日志 -## 0.6.1-preview +## 0.6.2-preview ### 发布说明 -这个小版本继续推进 `0.6.0-preview` 之后的架构收敛和搜索能力,不再把 0.6.0 当作后续开发的终点。 +这个小版本继续推进 `0.6.1-preview` 之后的数据源同步可靠性,不再把 0.6.0 当作后续开发的终点。 ### 架构 @@ -15,6 +15,12 @@ - 将 recordings/transcripts/speakers/tags 的搜索读模型拆到 `src/server/modules/recordings/search-read-model.ts`,搜索重建不再直接拼领域文档。 - 增加本地 SQLite FTS 查询清洗和 CJK n-gram fallback,避免把用户输入直接交给 FTS parser,同时为中文/日韩文搜索做 baseline。 +### 数据源 + +- Plaud 同步会连续读取账号可见记录列表,避免只处理首批记录。 +- 已有来源记录如果缺少本地音频,后续同步会在来源仍提供音频时尝试补齐本地归档。 +- 来源连接失败时的公开错误更稳定,避免把内部响应内容暴露给普通用户。 + ## 0.6.0-preview ### 当前状态 diff --git a/README.en.md b/README.en.md index a59c836..1c0ff62 100644 --- a/README.en.md +++ b/README.en.md @@ -16,7 +16,7 @@ FOSSA - Release status + Release status Self-hosting first @@ -35,7 +35,7 @@ BetterAINote brings voice records from DingTalk / A1, TicNote, Plaud, Feishu Minutes, iFLYTEK iFlyrec, and similar sources into one local workspace.
The focus is **private aggregation and unified management across voice platforms**, not one vendor identity.
Recordings, transcripts, speaker review, AI titles, tags, and search indexes stay around your own deployment first.
-Current version: `0.6.1-preview`. Self-hosting first. No npm package or public Docker image is published. +Current version: `0.6.2-preview`. Self-hosting first. No npm package or public Docker image is published.
@@ -79,7 +79,7 @@ BetterAINote is an independent project. Plaud is one supported source, not the p | Area | Status | | --- | --- | | Stage | `preview`, built for self-hosters and early feedback | -| Release | `0.6.1-preview` is the current preview release; stable release, npm package, and public Docker image are not published | +| Release | `0.6.2-preview` is the current preview release; stable release, npm package, and public Docker image are not published | | Package | `package.json` remains `private: true` | | Deployment | Local machine, home server, private server, or container environment you control | | Compatibility | API shape, provider capability, and settings may still change before the first stable release | @@ -141,7 +141,7 @@ Do not commit `.env.local`, databases, audio archives, account screenshots, or r | --- | --- | | DingTalk / A1 | Syncs accessible recordings with the credentials configured in settings. Source detail, audio, and summary availability depend on the account. | | TicNote | Supports China / international regions. Can sync records, archive available audio, and attempt title write-back when enabled. | -| Plaud | Supported as a recording source. Can sync records, archive available audio, and attempt title write-back when enabled. | +| Plaud | Supported as a recording source. Can sync visible records and archive available audio; later syncs can try to fill missing local audio for existing records when the source still provides it. | | Feishu Minutes | Can inspect or sync source metadata, transcripts, and summaries when account permissions allow. | | iFLYTEK iFlyrec | Focused on transcript record import and review. Audio and write-back depend on what the source exposes. | diff --git a/README.ja.md b/README.ja.md index 0baf7b5..8fbe042 100644 --- a/README.ja.md +++ b/README.ja.md @@ -16,7 +16,7 @@ FOSSA
- Release status + Release status Self-hosting first @@ -35,7 +35,7 @@ BetterAINote は DingTalk / A1、TicNote、Plaud、Feishu Minutes、iFLYTEK iFlyrec などの録音を、1 つのローカルワークスペースに集約します。
重点は、1 社のサービスではなく、**複数プラットフォームの音声資料を私有環境で集約し、統一管理すること**です。
録音、文字起こし、話者レビュー、AI タイトル、タグ、検索インデックスは、まず自分のデプロイ環境を中心に扱います。
-現在のバージョンは `0.6.1-preview` です。セルフホスト優先で、npm パッケージや公開 Docker イメージは配布していません。 +現在のバージョンは `0.6.2-preview` です。セルフホスト優先で、npm パッケージや公開 Docker イメージは配布していません。
@@ -79,7 +79,7 @@ BetterAINote は独立したプロジェクトです。Plaud は対応ソース | 項目 | 状態 | | --- | --- | | フェーズ | `preview`。セルフホスト利用者と早期フィードバック向け | -| リリース | `0.6.1-preview` が現在の preview release。安定版、npm パッケージ、公開 Docker イメージは未公開 | +| リリース | `0.6.2-preview` が現在の preview release。安定版、npm パッケージ、公開 Docker イメージは未公開 | | パッケージ | `package.json` は `private: true` のまま | | デプロイ | 自分で管理するローカルマシン、ホームサーバー、私有サーバー、コンテナ環境 | | 互換性 | 初回安定版までは API、provider 機能、設定項目が変わる可能性があります | @@ -141,7 +141,7 @@ bun run dev | --- | --- | | DingTalk / A1 | 設定された認証情報でアクセス可能な録音を同期します。詳細、音声、要約はアカウント権限に依存します。 | | TicNote | 中国 / 国際リージョンに対応。録音同期、取得可能な音声の保存、設定時のタイトル書き戻しを扱います。 | -| Plaud | 録音ソースとして対応。録音同期、取得可能な音声の保存、設定時のタイトル書き戻しを扱います。 | +| Plaud | 録音ソースとして対応。表示可能な録音の同期と取得可能な音声の保存を行い、既存レコードにローカル音声がない場合は後続同期で補完を試みます。 | | Feishu Minutes | 権限がある場合、ソースメタデータ、文字起こし、要約を確認または同期できます。 | | iFLYTEK iFlyrec | 文字起こし記録の取り込みと確認が中心です。音声と書き戻しはソース側の提供内容に依存します。 | diff --git a/README.ko.md b/README.ko.md index be449f6..53437c6 100644 --- a/README.ko.md +++ b/README.ko.md @@ -16,7 +16,7 @@ FOSSA
- Release status + Release status Self-hosting first @@ -35,7 +35,7 @@ BetterAINote는 DingTalk / A1, TicNote, Plaud, Feishu Minutes, iFLYTEK iFlyrec 같은 녹음 소스를 하나의 로컬 작업 공간으로 가져옵니다.
핵심은 특정 서비스 하나가 아니라 **여러 음성 플랫폼 자료를 사설 환경에 모아 통합 관리하는 것**입니다.
녹음, 전사, 화자 검토, AI 제목, 태그, 검색 인덱스는 우선 사용자가 제어하는 배포 환경에 남습니다.
-현재 버전은 `0.6.1-preview`입니다. 셀프 호스팅 우선이며 npm 패키지나 공개 Docker 이미지는 배포하지 않습니다. +현재 버전은 `0.6.2-preview`입니다. 셀프 호스팅 우선이며 npm 패키지나 공개 Docker 이미지는 배포하지 않습니다.
@@ -79,7 +79,7 @@ BetterAINote는 독립 프로젝트입니다. Plaud는 지원되는 소스 중 | 항목 | 상태 | | --- | --- | | 단계 | `preview`, 셀프 호스팅 사용자와 초기 피드백용 | -| 릴리스 | `0.6.1-preview`가 현재 preview release입니다. 안정 버전, npm 패키지, 공개 Docker 이미지는 아직 배포하지 않습니다 | +| 릴리스 | `0.6.2-preview`가 현재 preview release입니다. 안정 버전, npm 패키지, 공개 Docker 이미지는 아직 배포하지 않습니다 | | 패키지 | `package.json`은 `private: true`를 유지합니다 | | 배포 | 직접 제어하는 로컬 머신, 홈 서버, 사설 서버, 컨테이너 환경 | | 호환성 | 첫 안정 버전 전까지 API, provider 기능, 설정 항목이 바뀔 수 있습니다 | @@ -141,7 +141,7 @@ bun run dev | --- | --- | | DingTalk / A1 | 설정된 인증 정보로 접근 가능한 녹음을 동기화합니다. 상세 정보, 오디오, 요약은 계정 권한에 따라 달라집니다. | | TicNote | 중국 / 국제 리전을 지원합니다. 녹음 동기화, 가능한 오디오 보관, 활성화 시 제목 쓰기를 시도합니다. | -| Plaud | 녹음 소스로 지원합니다. 녹음 동기화, 가능한 오디오 보관, 활성화 시 제목 쓰기를 시도합니다. | +| Plaud | 녹음 소스로 지원합니다. 보이는 녹음을 동기화하고 가능한 오디오를 보관하며, 기존 기록에 로컬 오디오가 없으면 이후 동기화에서 보완을 시도합니다. | | Feishu Minutes | 권한이 있으면 소스 메타데이터, 전사, 요약을 확인하거나 동기화할 수 있습니다. | | iFLYTEK iFlyrec | 전사 기록 가져오기와 검토가 중심입니다. 오디오와 쓰기 기능은 소스가 제공하는 범위에 따릅니다. | diff --git a/README.md b/README.md index 2f38865..3d2b00b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ FOSSA
- Release status + Release status Self-hosting first @@ -35,7 +35,7 @@ BetterAINote 把钉钉 / A1、TicNote、Plaud、飞书妙记、讯飞听见等多平台语音资料收进一个本地工作台。
重点是**多来源语音资料的私有化集合与统一管理**,不是绑定某一个厂商。
录音、转写、说话人审阅、AI 标题、标签和搜索索引优先围绕你自己的部署运行。
-当前版本是 `0.6.1-preview`。自托管优先,不发布 npm 包或公开 Docker 镜像。 +当前版本是 `0.6.2-preview`。自托管优先,不发布 npm 包或公开 Docker 镜像。
@@ -79,7 +79,7 @@ BetterAINote 是独立项目。Plaud 只是其中一个支持的数据源,不 | 项目 | 说明 | | --- | --- | | 阶段 | `preview`,优先给愿意自托管和反馈的人试用 | -| 发布 | `0.6.1-preview` 是当前预发布版本;正式稳定版、Docker 镜像和 npm 包仍不发布 | +| 发布 | `0.6.2-preview` 是当前预发布版本;正式稳定版、Docker 镜像和 npm 包仍不发布 | | 包分发 | `package.json` 保持 `private: true` | | 部署方向 | 本机、家用服务器、私有服务器或你控制的容器环境 | | 兼容承诺 | 首个正式稳定版前,API、数据源能力和设置项仍可能调整 | @@ -141,7 +141,7 @@ bun run dev | --- | --- | | 钉钉 / A1 | 使用设置页要求的账号凭据同步可访问记录;来源详情、音频和摘要能力取决于账号可见内容。 | | TicNote | 支持中国区 / 国际区站点;可同步记录、归档可获取音频,并在启用时尝试把重命名写回来源。 | -| Plaud | 作为一个录音来源接入;可同步记录、归档可获取音频,并在启用时尝试把重命名写回来源。 | +| Plaud | 作为一个录音来源接入;可同步账号可见记录、归档可获取音频;已有记录缺本地音频时,后续同步会在来源仍可获取时尝试补齐。 | | 飞书妙记 | 可在账号权限允许时同步或查看来源元数据、逐字稿和摘要。 | | 讯飞听见 | 偏转写记录导入 / 查看场景;音频和标题写回能力按来源实际可用情况处理。 | diff --git a/bun.lock b/bun.lock index b1e9b0a..f70b22c 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "betterainote", @@ -18,7 +19,7 @@ "drizzle-orm": "0.45.2", "lucide-react": "0.554.0", "nanoid": "5.1.6", - "next": "16.2.4", + "next": "16.2.6", "next-themes": "0.4.6", "openai": "6.9.1", "react": "19.2.0", @@ -45,10 +46,13 @@ }, "overrides": { "defu": "6.1.5", + "esbuild": "0.27.7", + "kysely": "0.28.17", "picomatch": "4.0.4", "postcss": "8.5.10", "rollup": "4.59.0", "vite": "7.3.2", + "ws": "8.20.1", }, "packages": { "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -107,57 +111,57 @@ "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "3.3.2", "get-tsconfig": "4.13.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], "@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="], @@ -255,23 +259,23 @@ "@neon-rs/load": ["@neon-rs/load@0.0.4", "", {}, "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw=="], - "@next/env": ["@next/env@16.2.4", "", {}, "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw=="], + "@next/env": ["@next/env@16.2.6", "", {}, "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.6", "", { "os": "win32", "cpu": "x64" }, "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA=="], "@noble/ciphers": ["@noble/ciphers@2.2.0", "", {}, "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA=="], @@ -501,7 +505,7 @@ "es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], - "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "1.0.8" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], @@ -535,7 +539,7 @@ "js-tokens": ["js-tokens@10.0.0", "https://registry.npmmirror.com/js-tokens/-/js-tokens-10.0.0.tgz", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], - "kysely": ["kysely@0.28.16", "", {}, "sha512-3i5pmOiZvMDj00qhrIVbH0AnioVTx22DMP7Vn5At4yJO46iy+FM8Y/g61ltenLVSo3fiO8h8Q3QOFgf/gQ72ww=="], + "kysely": ["kysely@0.28.17", "", {}, "sha512-nbD8lB9EB3wNdMhOCdx5Li8DxnLbvKByylRLcJ1h+4SkrowVeECAyZlyiKMThF7xFdRz0jSQ2MoJr+wXux2y0Q=="], "libsql": ["libsql@0.5.29", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.29", "@libsql/darwin-x64": "0.5.29", "@libsql/linux-arm-gnueabihf": "0.5.29", "@libsql/linux-arm-musleabihf": "0.5.29", "@libsql/linux-arm64-gnu": "0.5.29", "@libsql/linux-arm64-musl": "0.5.29", "@libsql/linux-x64-gnu": "0.5.29", "@libsql/linux-x64-musl": "0.5.29", "@libsql/win32-x64-msvc": "0.5.29" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "arm", "x64", "arm64", ] }, "sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg=="], @@ -575,7 +579,7 @@ "nanostores": ["nanostores@1.3.0", "", {}, "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA=="], - "next": ["next@16.2.4", "", { "dependencies": { "@next/env": "16.2.4", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.4", "@next/swc-darwin-x64": "16.2.4", "@next/swc-linux-arm64-gnu": "16.2.4", "@next/swc-linux-arm64-musl": "16.2.4", "@next/swc-linux-x64-gnu": "16.2.4", "@next/swc-linux-x64-musl": "16.2.4", "@next/swc-win32-arm64-msvc": "16.2.4", "@next/swc-win32-x64-msvc": "16.2.4", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q=="], + "next": ["next@16.2.6", "", { "dependencies": { "@next/env": "16.2.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.6", "@next/swc-darwin-x64": "16.2.6", "@next/swc-linux-arm64-gnu": "16.2.6", "@next/swc-linux-arm64-musl": "16.2.6", "@next/swc-linux-x64-gnu": "16.2.6", "@next/swc-linux-x64-musl": "16.2.6", "@next/swc-win32-arm64-msvc": "16.2.6", "@next/swc-win32-x64-msvc": "16.2.6", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw=="], "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "19.2.0", "react-dom": "19.2.0" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], @@ -673,12 +677,10 @@ "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], - "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + "ws": ["ws@8.20.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w=="], "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], - "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "optionalDependencies": { "@types/react": "19.2.6" }, "peerDependencies": { "react": "19.2.0" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "optionalDependencies": { "@types/react": "19.2.6" }, "peerDependencies": { "react": "19.2.0" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], @@ -713,160 +715,8 @@ "sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "tsx/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], - "tsx/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - "vite/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], - "vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], - - "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], - - "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], - - "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], - - "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], - - "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], - - "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], - - "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], - - "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], - - "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], - - "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], - - "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], - - "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], - - "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], - - "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], - - "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], - - "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], - - "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], - - "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], - - "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], - - "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], - - "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], - - "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], - - "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], - - "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], - - "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], - - "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], - - "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], - - "vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], - - "vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], - - "vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], - - "vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], - - "vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], - - "vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], - - "vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], - - "vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], - - "vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], - - "vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], - - "vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], - - "vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], - - "vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], - - "vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], - - "vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], - - "vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], - - "vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], - - "vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], - - "vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], - - "vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], - - "vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], - - "vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], - - "vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], - - "vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], - - "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], } } diff --git a/docs/API.md b/docs/API.md index 3f317c5..5d4f1f5 100644 --- a/docs/API.md +++ b/docs/API.md @@ -11,7 +11,7 @@ | 阶段 | `preview`,首个正式 release 前仍可能调整路径、字段和错误码 | | 部署 | 私有部署优先,默认面向本机或可信网络 | | 鉴权 | 浏览器请求使用 Web app 会话;服务间访问应使用部署方控制的 secret 或 integration key | -| 发布 | CI 可以运行;`0.6.0-preview` 可作为预发布归档,Release / Docker 发布工作流默认关闭,需要手动授权 | +| 发布 | CI 可以运行;`0.6.2-preview` 是当前预发布版本,Release / Docker 发布工作流默认关闭,需要手动授权 | 所有示例都使用 `http://localhost:3001`。不要把示例里的占位符替换成真实凭据后提交到公开仓库。 diff --git a/docs/AUTO_SYNC.md b/docs/AUTO_SYNC.md index 0505d90..06422ea 100644 --- a/docs/AUTO_SYNC.md +++ b/docs/AUTO_SYNC.md @@ -12,8 +12,9 @@ BetterAINote 当前是 `preview`。自动同步用于私有部署中的后台检 - worker 按用户设置的同步间隔检查已启用数据源。 - 手动点击同步时,会立刻请求一次同步,不等待下一次定时检查。 +- 对支持连续列表读取的来源,单次同步会尽量覆盖账号当前可见的完整记录列表。 - 同步成功后,录音列表按录音开始时间倒序刷新。 -- 本地没有音频的录音不会进入私有转写。 +- 本地没有音频的录音不会进入私有转写;如果既有来源记录缺少本地音频,后续同步会在来源仍提供音频时尝试补齐归档。 - 私有转写排队和来源平台自己的处理状态是两层状态。 - Worker 状态会显示最近一次检查时间和下一次自动检查时间。 @@ -36,6 +37,7 @@ BetterAINote 当前是 `preview`。自动同步用于私有部署中的后台检 - 查看脱敏后的 app / worker 日志。 - 确认 `LOCAL_STORAGE_PATH` 对运行用户可写。 - 如果只有来源记录、没有音频,私有转写不会自动开始。 +- 如果历史记录曾经只导入元数据,重新同步可能补齐音频;补齐结果取决于来源当前是否仍允许获取音频。 ## 相关链接 diff --git a/docs/DATA_SOURCES.md b/docs/DATA_SOURCES.md index 1cafd7e..7b94b28 100644 --- a/docs/DATA_SOURCES.md +++ b/docs/DATA_SOURCES.md @@ -68,8 +68,9 @@ Plaud 是 BetterAINote 的一个录音来源。 设置建议: - 在 `Data Sources > Plaud` 配置账号连接。 -- 同步会把可见记录写入本地录音库。 +- 同步会连续读取账号可见记录,并写入本地录音库。 - 可获取音频时,可以归档到 `LOCAL_STORAGE_PATH` 并提交给私有转写。 +- 如果某条来源记录此前已经导入、但本地没有音频,后续同步会在来源仍提供音频时尝试补齐本地归档。 标题写回: @@ -110,6 +111,7 @@ Plaud 是 BetterAINote 的一个录音来源。 ## 普通用户限制说明 - 同步不等于下载音频;只有来源提供可用音频时才会进入本地归档。 +- 已有来源记录缺少本地音频时,后续同步可能补齐归档;如果来源不再提供音频,则仍只保留来源记录。 - 有来源逐字稿不等于有私有转写;私有转写需要音频和转写服务可用。 - 标题写回是可选能力,失败时本地标题仍是 BetterAINote 内的主显示名。 - 同一来源在不同地区站点、账号类型或权限下可能表现不同。 diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index a89873b..937ea77 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -11,7 +11,7 @@ | 项目 | 说明 | | --- | --- | | 阶段 | `preview` | -| 发布 | `0.6.0-preview` 是预发布基线;正式稳定版仍不发布 | +| 发布 | `0.6.2-preview` 是当前预发布版本;正式稳定版仍不发布 | | Docker | 可以本地构建;发布工作流手动触发且默认关闭 | | Release | Release notes 工作流手动触发且默认关闭,只在预发布归档或正式发布计划批准后使用 | | CI | `bun run format-and-lint`、`bun run type-check`、测试和构建可在 CI 中运行 | @@ -98,7 +98,7 @@ BetterAINote 默认使用本地 SQLite 和本地文件目录: | 词级时间数据 | `betterainote-words.db`,可用 `TRANSCRIPT_WORDS_DATABASE_PATH` 覆盖 | | 本地音频 | `LOCAL_STORAGE_PATH` | -备份时请把数据库目录和音频归档目录一起处理,否则恢复后可能出现记录存在但音频缺失的情况。 +备份时请把数据库目录和音频归档目录一起处理,否则恢复后可能出现记录存在但音频缺失的情况。恢复后再次同步时,BetterAINote 会在来源仍提供音频的前提下尝试补齐缺失的本地归档,但不要把它当作备份替代方案。 ## 配置分区 diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index b5ad30a..e2306ee 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -7,7 +7,7 @@ ## 当前状态 - BetterAINote 是独立的多来源私有录音工作台。 -- `0.6.0-preview` 是架构、数据库和 E2E 流程的 baseline SOT,不再把核心分层和 SQLite 形状当作后续可选优化。 +- `0.6.2-preview` 是当前预发布版本;`0.6.0-preview` 仍是架构、数据库和 E2E 流程的 baseline SOT。 - 当前优先级是自托管、数据安全、文档清晰和可维护的 provider 边界。 - 首个正式 release 前,API、设置项、数据源能力和 UI 细节仍可能调整。 - 不发布 npm 包;`package.json` 保持 `private: true`。 @@ -70,7 +70,7 @@ E2E 发现过的稳定性边界: | 私有部署 | 默认用户是自己控制实例、数据库、音频和服务凭据的人。 | | 服务分层 | 数据源、私有转写、AI 标题生成、播放和显示设置相互独立。 | | preview | 不承诺所有字段和 API 在首个正式 release 前稳定。 | -| 文档 | README 以英文为主入口,并维护简体中文、日文、韩文版本;技术 docs 可逐步补齐。 | +| 文档 | README.md 默认使用简体中文,并维护英文、日文、韩文版本;技术 docs 可逐步补齐。 | ## 架构 baseline @@ -131,6 +131,8 @@ DATABASE_PATH=./data/betterainote.db - Provider 专属解析、凭据处理和来源错误转换必须留在 provider 层。 - UI 只消费安全的能力标识和展示字段。 +- 列表型来源要避免只读取首批记录;如来源使用分页或游标,应通过 provider 层把账号可见记录完整交给同步层。 +- 已有来源记录如果缺少本地音频,且来源仍提供可获取音频,provider 和同步层应允许后续同步补齐归档,而不是把记录永久视为无需处理。 - 标题写回必须作为 provider capability 表达,不能假设所有来源都支持。 - 来源报告只能返回公开安全形状,不能把上游原始响应透出给浏览器。 - 公开错误信息要面向用户可处理的问题,不暴露内部请求上下文。 @@ -149,8 +151,8 @@ Issue 和 PR 模板应提醒贡献者只提供脱敏日志、字段名、HTTP ## 公开文档规则 -- README 以英文为主入口,并维护简体中文、日文、韩文版本。 -- 必须说明 `preview`、自托管优先、`0.6.0-preview` 可做预发布归档,但不发布 npm 包或公开镜像。 +- README.md 默认使用简体中文,并维护英文、日文、韩文版本。 +- 必须说明 `preview`、自托管优先、当前预发布版本,以及不发布 npm 包或公开镜像。 - License 口径统一为“个人免费,商业使用须事先取得书面授权;条款为 BetterAINote Additional Terms on top of Apache License 2.0”。 - 不写会让 BetterAINote 像某个来源派生项目的措辞。 - 不记录来源内部协议、未公开计划或本地研究材料。 diff --git a/package.json b/package.json index 6bf240b..e24605d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "betterainote", - "version": "0.6.1-preview", + "version": "0.6.2-preview", "private": true, "packageManager": "bun@1.2.20", "description": "Private self-hosted workspace for multi-platform voice record aggregation and unified management", @@ -39,7 +39,7 @@ "dev": "node scripts/dev-with-worker.mjs", "dev:web": "PORT=${PORT:-3001} WATCHPACK_POLLING=true CHOKIDAR_USEPOLLING=1 node scripts/run-with-local-env.mjs next dev --webpack", "dev:turbo": "PORT=${PORT:-3001} node scripts/run-with-local-env.mjs next dev", - "build": "next build", + "build": "next build --webpack", "start": "PORT=${PORT:-3001} node scripts/run-with-local-env.mjs next start", "worker": "node scripts/run-with-local-env.mjs bun src/worker/index.ts", "test": "vitest run", @@ -70,7 +70,7 @@ "drizzle-orm": "0.45.2", "lucide-react": "0.554.0", "nanoid": "5.1.6", - "next": "16.2.4", + "next": "16.2.6", "next-themes": "0.4.6", "openai": "6.9.1", "react": "19.2.0", @@ -95,9 +95,12 @@ }, "overrides": { "defu": "6.1.5", + "esbuild": "0.27.7", + "kysely": "0.28.17", "picomatch": "4.0.4", "postcss": "8.5.10", "rollup": "4.59.0", - "vite": "7.3.2" + "vite": "7.3.2", + "ws": "8.20.1" } } diff --git a/src/lib/data-sources/providers/plaud/client.ts b/src/lib/data-sources/providers/plaud/client.ts index e9df572..83e92b2 100755 --- a/src/lib/data-sources/providers/plaud/client.ts +++ b/src/lib/data-sources/providers/plaud/client.ts @@ -5,6 +5,7 @@ import type { PlaudFileDetailData, PlaudFileDetailResponse, PlaudFileListResponse, + PlaudRecording, PlaudRecordingsResponse, PlaudSummaryContent, PlaudTempUrlResponse, @@ -19,9 +20,27 @@ export interface PlaudUpdateFilenameResponse { data_file?: unknown; } +export interface ListAllPlaudRecordingsOptions { + skip?: number; + pageSize?: number; + maxPages?: number; + isTrash?: number; + sortBy?: string; + isDesc?: boolean; +} + export const DEFAULT_PLAUD_API_BASE = PLAUD_SERVERS[DEFAULT_SERVER_KEY].apiBase; const MAX_RETRIES = 3; const INITIAL_RETRY_DELAY = 1000; // 1 second +const DEFAULT_PLAUD_RECORDINGS_PAGE_SIZE = 100; +const MAX_PLAUD_RECORDINGS_PAGES = 500; +const PLAUD_WEB_REQUEST_HEADERS = { + Accept: "application/json, text/plain, */*", + Origin: "https://app.plaud.ai", + Referer: "https://app.plaud.ai/", + "User-Agent": + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", +}; export function normalizePlaudBearerToken(rawToken: string): string { return rawToken @@ -171,6 +190,31 @@ async function fetchContentLinkText(url: string): Promise { return text.length > 0 ? text : null; } +async function readPlaudErrorMessage(response: Response): Promise { + const fallback = response.statusText || "Request failed"; + + try { + if (typeof response.text === "function") { + const text = await response.text(); + if (!text.trim()) { + return fallback; + } + + try { + const parsed = JSON.parse(text) as PlaudApiError; + return parsed.msg || fallback; + } catch { + return fallback; + } + } + + const error = (await response.json()) as PlaudApiError; + return error.msg || fallback; + } catch { + return fallback; + } +} + /** * Sleep for specified milliseconds */ @@ -205,9 +249,10 @@ export class PlaudClient { const response = await fetch(url, { ...options, headers: { - ...options?.headers, + ...PLAUD_WEB_REQUEST_HEADERS, Authorization: `Bearer ${this.bearerToken}`, "Content-Type": "application/json", + ...options?.headers, }, }); @@ -221,8 +266,8 @@ export class PlaudClient { } if (!response.ok) { - const error = (await response.json()) as PlaudApiError; - const errorMessage = `Unable to connect to Plaud (${response.status}): ${error.msg || response.statusText}`; + const message = await readPlaudErrorMessage(response); + const errorMessage = `Unable to connect to Plaud (${response.status}): ${message}`; if ( response.status >= 500 && @@ -290,6 +335,49 @@ export class PlaudClient { ); } + async listAllRecordings( + options: ListAllPlaudRecordingsOptions = {}, + ): Promise { + const pageSize = + Number.isFinite(options.pageSize) && Number(options.pageSize) > 0 + ? Math.floor(Number(options.pageSize)) + : DEFAULT_PLAUD_RECORDINGS_PAGE_SIZE; + const maxPages = + Number.isFinite(options.maxPages) && Number(options.maxPages) > 0 + ? Math.floor(Number(options.maxPages)) + : MAX_PLAUD_RECORDINGS_PAGES; + let skip = + Number.isFinite(options.skip) && Number(options.skip) > 0 + ? Math.floor(Number(options.skip)) + : 0; + const recordingsById = new Map(); + + for (let page = 0; page < maxPages; page += 1) { + const response = await this.getRecordings( + skip, + pageSize, + options.isTrash ?? 0, + options.sortBy ?? "start_time", + options.isDesc ?? true, + ); + const items = response.data_file_list ?? []; + + for (const item of items) { + if (!recordingsById.has(item.id)) { + recordingsById.set(item.id, item); + } + } + + if (items.length < pageSize) { + break; + } + + skip += items.length; + } + + return Array.from(recordingsById.values()); + } + /** * Get temporary URL for downloading audio file * @param fileId - The recording file ID diff --git a/src/lib/data-sources/providers/plaud/definition.ts b/src/lib/data-sources/providers/plaud/definition.ts index c52652d..88aa6b8 100644 --- a/src/lib/data-sources/providers/plaud/definition.ts +++ b/src/lib/data-sources/providers/plaud/definition.ts @@ -243,8 +243,7 @@ export class PlaudSourceClient implements SourceProviderClient { } async listRecordings(): Promise { - const response = await this.client.getRecordings(0, 99999, 0); - const items = response.data_file_list ?? []; + const items = await this.client.listAllRecordings(); return await mapWithConcurrency( items, diff --git a/src/lib/sync/sync-recordings.ts b/src/lib/sync/sync-recordings.ts index f4eb844..9d10c56 100755 --- a/src/lib/sync/sync-recordings.ts +++ b/src/lib/sync/sync-recordings.ts @@ -385,6 +385,16 @@ function sourceRecordingTimingMatchesExisting( ); } +function sourceRecordingNeedsAudioBackfill( + existingRecording: typeof recordings.$inferSelect, + sourceRecording: SourceRecordingData, +) { + return ( + existingRecording.storagePath.trim().length === 0 && + Boolean(sourceRecording.audioDownload?.url) + ); +} + async function processSourceRecording( sourceRecording: SourceRecordingData, context: SyncContext, @@ -422,6 +432,10 @@ async function processSourceRecording( existingRecording, sourceRecording, ) && + !sourceRecordingNeedsAudioBackfill( + existingRecording, + sourceRecording, + ) && !sourceRecording.upstreamDeleted ) { return { status: "skipped" }; diff --git a/src/tests/plaud.test.ts b/src/tests/plaud.test.ts index dc7cc8b..a3b71d7 100755 --- a/src/tests/plaud.test.ts +++ b/src/tests/plaud.test.ts @@ -83,8 +83,12 @@ describe("PlaudClient", () => { `${DEFAULT_PLAUD_API_BASE}/device/list`, expect.objectContaining({ headers: expect.objectContaining({ + Accept: "application/json, text/plain, */*", Authorization: `Bearer ${mockBearerToken}`, "Content-Type": "application/json", + Origin: "https://app.plaud.ai", + Referer: "https://app.plaud.ai/", + "User-Agent": expect.stringContaining("Mozilla/5.0"), }), }), ); @@ -116,6 +120,32 @@ describe("PlaudClient", () => { }); describe("getRecordings", () => { + const makeRecording = (id: string, startTime = 1_700_000_000_000) => ({ + id, + filename: `${id}.mp3`, + keywords: [], + filesize: 1024, + filetype: "mp3", + fullname: `${id}.mp3`, + file_md5: `md5-${id}`, + ori_ready: true, + version: 1, + version_ms: 1, + edit_time: 0, + edit_from: "", + is_trash: false, + start_time: startTime, + end_time: startTime + 60_000, + duration: 60_000, + timezone: 8, + zonemins: 480, + scene: 0, + filetag_id_list: [], + serial_number: "device-1", + is_trans: false, + is_summary: false, + }); + it("should make request with default parameters", async () => { const mockResponse = { status: 0, @@ -158,6 +188,107 @@ describe("PlaudClient", () => { expect.any(Object), ); }); + + it("lists all recordings across skip/limit pages until the final short page", async () => { + const pageSize = 2; + mockFetch + .mockResolvedValueOnce({ + ok: true, + json: () => + Promise.resolve({ + status: 0, + msg: "success", + data_file_total: 2, + data_file_list: [ + makeRecording("page-1-a"), + makeRecording("page-1-b"), + ], + }), + }) + .mockResolvedValueOnce({ + ok: true, + json: () => + Promise.resolve({ + status: 0, + msg: "success", + data_file_total: 1, + data_file_list: [makeRecording("page-2-a")], + }), + }); + + const recordings = await client.listAllRecordings({ pageSize }); + + expect(recordings.map((recording) => recording.id)).toEqual([ + "page-1-a", + "page-1-b", + "page-2-a", + ]); + expect(fetch).toHaveBeenNthCalledWith( + 1, + `${DEFAULT_PLAUD_API_BASE}/file/simple/web?skip=0&limit=2&is_trash=0&sort_by=start_time&is_desc=true`, + expect.any(Object), + ); + expect(fetch).toHaveBeenNthCalledWith( + 2, + `${DEFAULT_PLAUD_API_BASE}/file/simple/web?skip=2&limit=2&is_trash=0&sort_by=start_time&is_desc=true`, + expect.any(Object), + ); + }); + + it("deduplicates repeated recordings while paginating", async () => { + mockFetch + .mockResolvedValueOnce({ + ok: true, + json: () => + Promise.resolve({ + status: 0, + msg: "success", + data_file_total: 2, + data_file_list: [ + makeRecording("shared"), + makeRecording("first"), + ], + }), + }) + .mockResolvedValueOnce({ + ok: true, + json: () => + Promise.resolve({ + status: 0, + msg: "success", + data_file_total: 1, + data_file_list: [makeRecording("shared")], + }), + }); + + const recordings = await client.listAllRecordings({ pageSize: 2 }); + + expect(recordings.map((recording) => recording.id)).toEqual([ + "shared", + "first", + ]); + }); + + it("stops paginating at the configured safety page limit", async () => { + mockFetch.mockResolvedValue({ + ok: true, + json: () => + Promise.resolve({ + status: 0, + msg: "success", + data_file_total: 1, + data_file_list: [makeRecording("repeat")], + }), + }); + + const recordings = await client.listAllRecordings({ + pageSize: 1, + maxPages: 2, + }); + + expect(recordings).toHaveLength(1); + expect(fetch).toHaveBeenCalledTimes(2); + }); }); describe("getTempUrl", () => { @@ -358,6 +489,19 @@ describe("PlaudClient", () => { ); }); + it("falls back to the HTTP status text when the service returns a non-JSON error page", async () => { + mockFetch.mockResolvedValueOnce({ + ok: false, + status: 403, + statusText: "Forbidden", + text: () => Promise.resolve(""), + }); + + await expect(client.listDevices()).rejects.toThrow( + "Unable to connect to Plaud (403): Forbidden", + ); + }); + it("should throw error when fetch fails", async () => { mockFetch.mockRejectedValueOnce(new Error("Network error")); diff --git a/src/tests/sync.test.ts b/src/tests/sync.test.ts index 90852fb..9ebb964 100644 --- a/src/tests/sync.test.ts +++ b/src/tests/sync.test.ts @@ -243,6 +243,209 @@ describe("Sync", () => { expect(result.updatedRecordings).toBe(0); }); + it("downloads audio for same-version recordings that were imported without local audio", async () => { + const updateSet = vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + returning: vi.fn().mockResolvedValue([]), + }), + }); + (db.update as Mock).mockReturnValue({ set: updateSet }); + (db.select as Mock) + .mockReturnValueOnce({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi + .fn() + .mockResolvedValue([{ autoTranscribe: false }]), + }), + }), + }) + .mockReturnValueOnce({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi.fn().mockResolvedValue([ + { + id: "rec-1", + sourceVersion: "1000", + storagePath: "", + downloadedAt: null, + duration: 60000, + startTime: new Date("2024-01-01T10:00:00Z"), + endTime: new Date("2024-01-01T10:01:00Z"), + }, + ]), + }), + }), + }); + + (getEnabledSourceConnectionsForUser as Mock).mockResolvedValue([ + { provider: "plaud", userId: mockUserId }, + ]); + (createSourceProviderClient as Mock).mockReturnValue({ + listRecordings: vi.fn().mockResolvedValue([ + { + sourceProvider: "plaud", + sourceRecordingId: "source-rec-1", + filename: "Recovered Audio.mp3", + durationMs: 60000, + startTime: new Date("2024-01-01T10:00:00Z"), + endTime: new Date("2024-01-01T10:01:00Z"), + version: "1000", + filesize: 2048, + audioDownload: { + url: "https://example.test/audio.mp3", + fileExtension: "mp3", + }, + artifacts: null, + }, + ]), + }); + + const result = await syncRecordingsForUser(mockUserId); + + expect(result.errors).toEqual([]); + expect(result.newRecordings).toBe(0); + expect(result.updatedRecordings).toBe(1); + expect(downloadSourceAudioBufferMock).toHaveBeenCalledWith( + "plaud", + expect.objectContaining({ + url: "https://example.test/audio.mp3", + archiveBaseName: "Recovered Audio", + fileExtension: "mp3", + contentType: "audio/mpeg", + }), + ); + expect(uploadFileMock).toHaveBeenCalledWith( + expect.stringContaining("/plaud/Recovered Audio.mp3"), + expect.any(Buffer), + "audio/mpeg", + ); + expect(updateSet).toHaveBeenCalledWith( + expect.objectContaining({ + storagePath: expect.stringContaining( + "/plaud/Recovered Audio.mp3", + ), + downloadedAt: expect.any(Date), + filesize: 2048, + }), + ); + }); + + it("keeps same-version recordings skipped when local audio already exists", async () => { + (db.select as Mock) + .mockReturnValueOnce({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi + .fn() + .mockResolvedValue([{ autoTranscribe: false }]), + }), + }), + }) + .mockReturnValueOnce({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi.fn().mockResolvedValue([ + { + id: "rec-1", + sourceVersion: "1000", + storagePath: "user-123/plaud/existing.mp3", + downloadedAt: new Date("2024-01-01T10:02:00Z"), + duration: 60000, + startTime: new Date("2024-01-01T10:00:00Z"), + endTime: new Date("2024-01-01T10:01:00Z"), + }, + ]), + }), + }), + }); + + (getEnabledSourceConnectionsForUser as Mock).mockResolvedValue([ + { provider: "plaud", userId: mockUserId }, + ]); + (createSourceProviderClient as Mock).mockReturnValue({ + listRecordings: vi.fn().mockResolvedValue([ + { + sourceProvider: "plaud", + sourceRecordingId: "source-rec-1", + filename: "Existing Audio.mp3", + durationMs: 60000, + startTime: new Date("2024-01-01T10:00:00Z"), + endTime: new Date("2024-01-01T10:01:00Z"), + version: "1000", + audioDownload: { + url: "https://example.test/audio.mp3", + fileExtension: "mp3", + }, + artifacts: null, + }, + ]), + }); + + const result = await syncRecordingsForUser(mockUserId); + + expect(result.newRecordings).toBe(0); + expect(result.updatedRecordings).toBe(0); + expect(downloadSourceAudioBufferMock).not.toHaveBeenCalled(); + expect(uploadFileMock).not.toHaveBeenCalled(); + }); + + it("does not force-download same-version recordings when the source has no audio URL", async () => { + (db.select as Mock) + .mockReturnValueOnce({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi + .fn() + .mockResolvedValue([{ autoTranscribe: false }]), + }), + }), + }) + .mockReturnValueOnce({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ + limit: vi.fn().mockResolvedValue([ + { + id: "rec-1", + sourceVersion: "1000", + storagePath: "", + downloadedAt: null, + duration: 60000, + startTime: new Date("2024-01-01T10:00:00Z"), + endTime: new Date("2024-01-01T10:01:00Z"), + }, + ]), + }), + }), + }); + + (getEnabledSourceConnectionsForUser as Mock).mockResolvedValue([ + { provider: "plaud", userId: mockUserId }, + ]); + (createSourceProviderClient as Mock).mockReturnValue({ + listRecordings: vi.fn().mockResolvedValue([ + { + sourceProvider: "plaud", + sourceRecordingId: "source-rec-1", + filename: "Source Only.mp3", + durationMs: 60000, + startTime: new Date("2024-01-01T10:00:00Z"), + endTime: new Date("2024-01-01T10:01:00Z"), + version: "1000", + audioDownload: null, + artifacts: null, + }, + ]), + }); + + const result = await syncRecordingsForUser(mockUserId); + + expect(result.newRecordings).toBe(0); + expect(result.updatedRecordings).toBe(0); + expect(downloadSourceAudioBufferMock).not.toHaveBeenCalled(); + expect(uploadFileMock).not.toHaveBeenCalled(); + }); + it("updates same-version recordings when normalized source timing changes", async () => { const updateReturning = vi.fn().mockResolvedValue([]); const updateWhere = vi.fn().mockReturnValue({