
Anarlog 移动端架构解析基于 Expo SDK 57、UniFFI SQLite 与实时转写流水线的本地优先应用【免费下载链接】anarlogOpen source Granola AI Alternative项目地址: https://gitcode.com/GitHub_Trending/hy/anarlog导读本文以仓库文档 apps/mobile/AGENTS.md由 apps/mobile/CLAUDE.md 通过AGENTS.md显式委托引用为核心系统讲解 Anarlog 移动端Android/iOS的工程架构与实践约定。该移动端是 Anarlog 桌面端的移动伴侣采用 Local-first本地优先数据模型把录音 → 实时转写 → 笔记的完整链路搬到手机上并通过云端同步与桌面端保持一致。读完本文你将掌握移动端开发命令与构建流程、SQLite 数据库层如何通过 UniFFI 桥接 Rust 核心、认证与 Pro 计费门禁的实现方式、录音与转写的实时/批处理双通道设计以及 E2EE 云同步的接入原理。一、项目定位与技术栈概览Anarlog Mobile 是 Anarlog开源的 Granola AI 替代品的移动端应用技术选型为Expo SDK 57React Native 0.86.3 React 19.2.3使用 Expo Router 组织页面。移动端不是桌面的简单移植而是本地优先的独立客户端即使用户未登录、不订阅任何付费计划也能使用本地笔记与本地录音功能云同步与 Anarlog 托管的 AI 模型能力则通过三周共享 Pro 试用期与付费 Pro 权益解锁详见 apps/mobile/AGENTS.md 的 Architecture 一节。从 apps/mobile/package.json 的依赖列表可以直观看到其技术分层Expo 生态expo-audio录音、expo-file-system文件读写、expo-secure-store/expo-local-authentication密钥与应用锁、expo-router路由、expo-dev-client开发调试、expo-widgetsiOS 小组件、expo-sharing等工作区包anlg/db-runtime、anlg/db-react数据库响应式查询、anlg/mobile-bridge调用 Rust 桥、anlg/supabase、anlg/provider-validation、anlg/user-error等服务端supabase/supabase-js认证与sentry/react-native错误上报。多环境变体配置见 apps/mobile/app.config.ts通过APP_VARIANT环境变量区分dev/staging/stable三套 App 名称、图标、URL schemeanarlog-dev/anarlog-staging/anarlog与 bundle identifier并约定默认 API 地址开发态http://localhost:3001正式态https://api.anarlog.so未设置SUPABASE_URL时则进入免认证的本地开发模式。二、开发命令与构建流程2.1 常用命令AGENTS.md 明确指出两条核心命令见 apps/mobile/AGENTS.md Commands 一节# 启动 iOS 开发或替换为 android pnpm -F anlg/mobile ios # 类型检查 pnpm -F anlg/mobile typecheck两条命令都通过 workspace 过滤符-F anlg/mobile精确作用于移动端包。其中ios/android脚本内部使用 dotenvx 从仓库根目录的.env.supabase加载 Supabase 环境变量--ignore MISSING_ENV_FILE表示文件缺失时不报错再以expo start --dev-client方式启动因此依赖 Expo Dev Client 运行。2.2 构建、测试与版本管理apps/mobile/package.json 中值得关注的更多脚本脚本作用ios:build/android:build先执行cargo xtask mobile-bridge ios或android重新编译 Rust 桥接层再以expo run:ios/expo run:android进行原生构建test使用 Node 内置 test runner 与--experimental-strip-types直接运行 TypeScript 测试test:billing-handoff专门验证桌面→移动端的计费交接逻辑src/auth/billing-handoff.test.mjstest:transcription-limits验证转写配额边界src/data/transcription-limits.test.mjstypechecktsc --noEmit全量类型检查AGENTS.md 还强调了一个容易踩坑的版本规则移动端市场版本号独立存放于 apps/mobile/release-version.json与桌面的release-version.json互不影响升级时必须显式执行node scripts/release-version.mjs --mobile major.minor.patchapp.config.ts会读取该文件并把version注入 ExpoConfig 的ios.version/android.version确保商店版本与构建产物一致。三、总体架构本地优先 云端同步AGENTS.md 的 Architecture 一节给出了移动端的骨架可归纳为四层React Native UIexpo-router 页面 │ useLiveQuery响应式 SQL 查询 ▼ anlg/db-react ──► anlg/db-runtimeLiveQueryClient / TransactionClient 契约 │ ▼ src/db/client.tsmobile-bridge 调用封装 │ UniFFI 桥 ▼ crates/mobile-bridgeRust──► SQLitecanonical schema由 crates/db-app 维护关键事实如下数据库契约层src/db/通过 UniFFIcrates/mobile-bridge传输层实现anlg/db-runtime的LiveQueryClient/TransactionClient两个契约最终由anlg/db-react的useLiveQuery消费。契约的具体实现见 src/db/client.tsmobileLiveQueryClientexecutesubscribe与mobileTransactionClientexecuteTransaction在文件末尾导出而 src/db/index.ts 通过createUseLiveQuery(liveQueryClient)生成移动端专属的useLiveQuery业务页面直接引用。Schema 归属canonical SQLite schema 与全部迁移由crates/db-app统一维护移动端与桌面共用同一套表结构与迁移脚本不另起炉灶。数据语义对齐桌面src/data/严格镜像桌面的查询语义——canonical 的创建会话事务、以 ProseMirror JSON 存储的笔记文档约定note.id session_id、以及session-audio:sessionId形式的附件行全部与桌面端一一对应。会话 SQL 参考基准AGENTS.md 特别指出会话相关 SQL 必须与桌面端 apps/desktop/src/session/queries.ts 保持语义一致它是 session SQL 的权威参考。在src/db/client.ts的getBridge()实现中可以看到底层细节数据库文件固定为文档目录下SQLite/anarlog.db通过MobileDbBridge.open(databasePath, disabled)打开第二个参数为加密相关配置此处禁用随后调用configureAttachmentStorage将附件存储指向文档目录与缓存目录——这正是本地优先物理落盘的基础。四、数据库层深入响应式查询与事务4.1 LiveQueryClient 与实时刷新移动端 UI 的数据自动刷新完全依赖 LiveQuery 机制。src/db/client.ts中的subscribe实现src/db/client.ts展示了核心模式把 SQL 与 JSON 序列化参数下发给 Rust 桥getBridge().subscribe(sql, params, listener)获得一个订阅 ID桥层回调listener.onResult/onErrorJS 侧对结果行做JSON.parse后交给options.onData返回的Unsubscribe函数负责幂等退订且所有异常路径都会进入captureOperationalError错误上报通道。这样当转写结果、笔记正文、录音状态等底层表发生变化时订阅的页面会收到增量行集并自动重渲染无需手动刷新。4.2 事务执行与批量语句executeTransaction接收TransactionStatement[]一组 SQL 参数整体序列化后一次性交给 Rust 桥原子执行。这在落库转写结果这类多表写操作中尤其重要例如 src/data/transcribe.ts 在批处理转写成功时把软删除旧 transcripts 行 → 插入新 transcript 行 → 标记附件transcript_statuscomplete三步放进同一个事务保证用户在任何时刻都不会看到半成品状态。五、认证、计费门禁与桌面交接5.1 Supabase 认证与本地会话移动端认证基于supabase/supabase-js见 src/auth/client.ts使用AsyncStorage持久化会话storageKey 根据 Supabase URL 的主机名动态生成例如sb-xxx-auth-token并开启autoRefreshToken/persistSession监听AppStateApp 回到前台时startAutoRefresh()退到后台时stopAutoRefresh()避免后台空转刷新 token无 Supabase 环境变量时supabase客户端为null即 AGENTS.md 所述的 bypass 模式——本地开发、不做任何计费门禁。5.2 桌面浏览器交接browser handoff登录流程支持桌面端交接通过/auth?flowdesktopschemeanarlog深链把桌面会话带到移动端scheme 与 app.config.ts 中的变体配置一致实现桌面已登录、手机免输入的体验。5.3 JWT Claims 计费门禁Hermes 限制下的移植Pro 权益判定采用与桌面端 packages/supabase/src/billing.ts 完全相同的 JWT-claims 逻辑但由于jose 库无法在 Hermes 引擎上运行移动端在 src/auth/billing.ts 中手动移植了解码与判定逻辑并保持语义同步。要点decodeJwtPayload仅做展示/门禁用非安全解码源码注释明确 display/gating only, never security从 JWT 的 payload 段提取entitlements、subscription_status、trial_end等字段deriveBillingInfo综合订阅状态与试用期剩余天数计算planfree/trial/pro并规定试用时钟到期与暂停订阅会覆盖过期的 entitlements让所有客户端统一 fail closedsrc/auth/billing.ts未授权使用 Anarlog 托管模型时抛出ProRequiredError提示用户可在设置中选择自带 API Key 的 BYOK 提供商继续使用src/auth/billing.ts。转写流程中捕获该错误后会把状态复位为idle不进入失败重试见 src/data/transcribe.ts。六、录音与转写流水线6.1 录音PCM 流 → WAV 落盘录音入口是 src/audio/use-session-recorder.ts基于expo-audio的useAudioStream固定16kHz、单声道、int16 PCM文件顶部常量STREAM_SAMPLE_RATE 16_000、STREAM_CHANNELS 1每个音频缓冲到达时先由SessionWavWriter.append写入 WAV 文件路径为documents/sessions/sessionId/audio.wav同时计算pcmAmplitude驱动录音振幅 UI启动前依次申请麦克风权限Android 13 还需申请POST_NOTIFICATIONS通知权限用于常驻通知随后通过setAudioModeAsync开启allowsRecordingallowsBackgroundRecording保证锁屏/后台可继续录音失败类型被枚举为permission_denied、notification_permission_denied、start_failed、media_services_reset、native_error、save_failed六类src/audio/use-session-recorder.ts便于精准上报与 UI 提示。录音完成后catalogSessionAudio会把该音频登记为session-audio:sessionId附件行src/data/audio-catalog.ts进入统一的数据目录。6.2 转写双通道实时live_capture与批处理batch_transcriptionAGENTS.md 明确了移动端没有设备端 STT 模型转写全部走云端或 BYOK实时转写录音过程中 PCM 数据同时流式发送到 Anarlog Pro 或受支持的 BYOK 实时模型。实现见 src/data/live-transcription.ts连接${env.apiUrl}/stt/listen的 WebSocket自动切换wss:协议携带provideranarlog、modelcloud、encodinglinear16、sample_rate、channels等参数实时结果写入 canonical 的live_capture转录行并伴随transcript_live_state/transcript_live_deltas序列化增量src/data/live-transcription.ts保证断点续传与桌面端增量语义一致。批处理转写当使用导入的音频、批处理模型或实时链路失败时走批处理通道。实现见 src/data/transcribe.ts将音频 POST 到${env.apiUrl}/stt/listenprovideranarlog成功后将结果写入sourcebatch_transcription的 transcript 行整会话替换语义并附带可选的说话人索引提示provider_speaker_index若提供商只返回纯文本而无词级时间戳则按每词 400ms 生成合成时间轴SYNTHETIC_TEXT_WORD_MS与桌面batch.ts的synthetic_text回退行为一致见 src/data/transcribe.ts。完成标记两条路径成功后都会把对应session_attachments的metadata_json.transcript_status置为completeMARK_COMPLETE_SQL供查询层区分已转写与待转写。值得注意的工程细节请求超时按文件大小动态预算基础 60 秒每 KiB 叠加上传处理各 8ms上限 900 秒避免长录音被误杀src/data/transcribe.ts响应做了严格的边界防护MAX_TRANSCRIPTION_WORDS、MAX_TRANSCRIPTION_RESPONSE_BYTES空结果绝不会标记完成保留点击重试入口src/data/transcribe.ts并发通过TranscriptionAdmission(2, 32)限流同时最多 2 个在跑、队列上限 32并对永久性失败如音频缺失、过大加入自动重试黑名单避免反复请求。6.3 BYOK原生适配器与 OpenAI 兼容端点自带 KeyBYOK的差异化处理在 AGENTS.md 中有明确分工原生 BYOK 转写复用owhisper-client适配器经mobile-bridge调用Rust 侧适配器在 crates/owhisper-clientCustom 转写走 OpenAI 兼容的 HTTP 端点requestProviderTranscription见 src/data/provider-transcription.ts。同时转写前会通过resolveProvider(stt)与batchTranscriptionModel判断所选提供商是否支持批处理若该提供商只支持实时live-only保存的录音将保留可用状态提示用户更换提供商后重试而不是丢失录音对应 AGENTS.md 中 Live-only providers keep failed recordings available for retry 的规则代码中的stt_live_only错误码见 src/data/transcribe.ts。七、云同步与 E2EE 恢复同步能力同样由 Rust 桥提供。src/db/client.ts暴露了一组与cloudsync相关的接口startSync/stopSync/syncNow/getSyncStatus分别对应桥层的startCloudsync/stopCloudsync/cloudsyncSyncNow/cloudsyncStatussrc/db/client.tsMobileSyncStatus结构体包含configured、running、has_unsent_changes、last_sync_at_ms、last_error、consecutive_failures等字段供同步状态页展示E2EE 恢复密钥generateE2eeRecoveryKey/inspectE2eeRecoveryKey生成并校验恢复密钥格式校验为 43 位 base64url 的公钥与 22 位 keyId设备注册generateE2eeDeviceEnrollmentKey/inspectE2eeDeviceEnrollmentKey/openE2eeDeviceEnrollment完成新设备加入工作区Workspace的密钥协商副本引导bootstrapE2eeReplica是完整流程——先用恢复密钥校验身份向 API 请求副本凭据requestReplicaCredentials见 src/sync/replica-credentials.ts再以witnessEndpoint /sync/e2ee/witness/workspaceId配置 E2EE 副本最后自动startSync()开始同步src/db/client.ts。同步的运行时生命周期AppState 驱动的触发、设备注册、身份管理等集中在 src/sync/ 目录例如controller.ts同步控制器、mobile-sync-lifecycle.tsx生命周期接入、opt-in.ts用户选择加入等并有配套测试controller.test.mjs、device-enrollment.test.mjs、replica-credentials.test.mjs等验证状态机行为。八、工程规则与约束AGENTS.md 的 Rules 一节是移动端开发必须遵守的四条纪律值得开发者重点内化本地写入永不等待网络所有本地写操作立即返回远程副作用上传、同步、转写回调随后以 best-effort 方式执行——这是本地优先体验不被弱网拖垮的根本保证Schema/SQL 与桌面严格对齐不发明移动端专属列或枚举。canonical schema 与迁移在crates/db-app会话 SQL 以 apps/desktop/src/session/queries.ts 为基准营销版本独立管理移动端版本存于 apps/mobile/release-version.json用node scripts/release-version.mjs --mobile major.minor.patch更新勿与桌面版本混淆UX 以设计文档为准界面交互的参考规范位于 apps/mobile/design/README.md。九、小结Anarlog Mobile 展示了一种本地优先 Rust 核心 云端增强的移动端工程范式通过 UniFFI 把 SQLite、E2EE 与同步逻辑沉淀在 Rust 层crates/mobile-bridge 与 crates/db-appJS 侧仅负责契约化调用与 UI转写采用实时流式 批处理回退双通道既保证即时反馈又不丢失任何录音计费门禁则通过移植自桌面的 JWT-claims 逻辑在 Hermes 上保持一致。如果你正在设计跨端本地优先应用本文涉及的响应式 SQL 契约层、SQL 语义跨端对齐、Hermes 环境下的依赖移植、失败可重试的转写状态机都是可以直接借鉴的落地经验。【免费下载链接】anarlogOpen source Granola AI Alternative项目地址: https://gitcode.com/GitHub_Trending/hy/anarlog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考