hailin
5ff8bda99e
feat(notification): 完整站内消息推送体系 (Phase 1-4)
...
## Phase 1 — 精准推送基础
- 新增 notification-service 微服务 (port 3013)
- DB迁移 007: notifications, notification_reads, notification_tenant_targets 表
- DB迁移 008: tenant_tags, tenant_tag_assignments, notification_user_targets 表
+ notifications 表新增 target_tag_ids/target_tag_logic/target_plans/target_statuses/channel_key 字段
- auth-service: TenantTagController — 租户标签 CRUD + 批量分配 (9个接口)
- notification-service 支持 7 种推送目标类型:
ALL / SPECIFIC_TENANTS / SPECIFIC_USERS / BY_TENANT_TAG(ANY|ALL) / BY_PLAN / BY_TENANT_STATUS / BY_SEGMENT
- Web Admin: /tenant-tags 标签管理页 + 通知表单全面扩展
## Phase 2 — 通知频道与用户偏好
- DB迁移 009: notification_channels (6个预置频道) + user_notification_preferences
+ notification_segment_members 表 (Phase 4 人群包)
- notification-service: ChannelRepository + NotificationChannelController
(频道 CRUD + 用户偏好 API,强制频道不可关闭)
- Web Admin: /notification-channels 频道管理页
- Flutter: NotificationPreferencesPage — 用户按频道 toggle 订阅,profile页新增入口
## Phase 3 — Campaign 活动与数据分析
- DB迁移 010: notification_campaigns, campaign_execution_log, notification_event_log 表
- notification-service: CampaignRepository + CampaignAdminController
(ONCE/RECURRING调度, 排期/取消/删除, 发送量/阅读率统计)
- Web Admin: /campaigns 推送活动管理页 (状态机 + 数据统计弹窗)
## Phase 4 — 事件触发与人群包
- EventTriggerService: Redis Stream 消费者,监听并自动创建通知:
billing.payment_failed / billing.quota_warning / tenant.registered / alert.fired
- SegmentRepository + SegmentAdminController (全量同步/增量添加/删除)
- Web Admin: /segments 人群包管理页 (成员管理 + ETL全量替换)
## 基础设施
- Kong: 新增 notification-service 服务 + 6条路由 + JWT插件
- Docker Compose: 新增 notification-service 容器 (13013:3013)
- notification-service 新增 ioredis 依赖 (Redis Stream 消费)
## Flutter (APK需手动编译)
- 新增路由: /notifications/inbox, /notifications/preferences
- 新增: NotificationInboxPage, NotificationPreferencesPage
- 新增: ForceReadNotificationDialog (强制阅读拦截弹窗)
- profile页: 站内消息行(未读角标) + 通知偏好设置入口
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 22:33:40 -08:00
hailin
2f17266455
feat(referral): implement full referral system across all layers
...
## Overview
完整实现 IT0 推荐裂变系统,涵盖后端微服务、基础设施、Flutter 移动端、Next.js Web Admin。
## Backend — referral-service (packages/services/referral-service/)
### 架构设计
- 遵循 billing-service 模式:DataSource 直接访问 public schema(非 TenantAwareRepository)
- 推荐单元为租户级别(tenant-level),不区分租户内用户
- 最大 2 层推荐深度(L1 直接推荐 / L2 间接推荐)
- 推荐码格式:`IT0-{tenantPrefix3}-{random4}` 例:`IT0-ACM-X9K2`
### 领域实体(5个,均在 public schema)
- `referral_codes`:每个租户唯一推荐码,记录点击量
- `referral_relationships`:推荐关系,状态流转 PENDING→ACTIVE→REWARDED→EXPIRED
- `referral_rewards`:积分奖励记录,支持 PENDING/APPLIED/EXPIRED
- `referral_stats`:每租户聚合统计(直推数、积分总量等)
- `referral_processed_events`:Redis Stream 幂等性去重表
### 奖励规则
- Pro 套餐首次付款:推荐人 $15(1500分)/ 被推荐人 $5(500分)
- Enterprise 套餐首次付款:推荐人 $50(5000分)/ 被推荐人 $20(2000分)
- 续订奖励:付款金额 10%,最多持续 12 个月
- 奖励触发:监听 Redis Stream `events:payment.received`,消费者组 `referral-service`
### Use Cases(6个)
- `GetMyReferralInfoUseCase`:获取/自动创建推荐码,返回分享链接
- `ValidateReferralCodeUseCase`:验证码格式 + 存在性(公开接口,注册前使用)
- `RegisterWithCodeUseCase`:注册时绑定推荐关系,防止自推荐/重复注册
- `ConsumePaymentReceivedUseCase`:消费支付事件,发放首次/续订奖励,含幂等保护
- `GetReferralListUseCase`:分页查询推荐列表和奖励记录
- `GetPendingCreditsUseCase`:供 billing-service 查询待抵扣积分并标记已使用
### REST Controllers(3个)
- `ReferralController` (/api/v1/referral):用户端,JWT 验证
- GET /me — 我的推荐码与统计
- GET /me/referrals — 我的推荐列表(分页)
- GET /me/rewards — 我的奖励记录(分页)
- GET /validate?code=xxx — 公开验证推荐码(注册页使用)
- `ReferralInternalController` (/api/v1/referral/internal):服务间调用,X-Internal-Api-Key 验证
- POST /register — auth-service 注册后回调,绑定推荐关系
- GET /:tenantId/pending-credits — billing-service 查询待抵扣金额
- POST /:tenantId/apply-credits — billing-service 账单生成后标记积分已使用
- `ReferralAdminController` (/api/v1/referral/admin):管理员端,JWT + platform_admin 角色
- GET /relationships — 全量推荐关系(可按状态过滤,分页)
- GET /rewards — 全量奖励记录(可按状态过滤,分页)
- GET /stats — 平台汇总统计
## Infrastructure
### database migration (packages/shared/database/migrations/006-create-referral-tables.sql)
创建 5 张表,含必要索引(tenantId、code、status、createdAt)
### docker-compose.yml
新增 referral-service 服务定义(port 13012:3012),healthcheck 基于 HTTP 200,
api-gateway depends_on 中添加 referral-service healthy 条件
### kong.yml (packages/gateway/config/kong.yml)
新增 3 组路由:
- `referral-routes`:/api/v1/referral(JWT 插件,转发用户请求)
- `referral-admin-routes`:/api/v1/referral/admin(JWT 插件,管理员)
- `referral-validate-public`:/api/v1/referral/validate(无 JWT,注册页调用)
注:internal 路由不暴露到 Kong,仅服务间直接调用
## auth-service 集成 (packages/services/auth-service/src/application/services/auth.service.ts)
注册成功后(register + registerWithNewTenant 两个路径)fire-and-forget 调用
referral-service 内部接口 POST /api/v1/referral/internal/register,
传入 tenantId + referralCode(可选),使用 Node.js 内置 http 模块(无新依赖)
## Flutter 移动端 (it0_app/lib/features/referral/)
### 数据层
- `referral_info.dart`:ReferralInfo / ReferralItem / RewardItem 模型,含格式化 getter
- `referral_repository.dart`:Dio HTTP 请求 + Riverpod referralRepositoryProvider
### 状态管理(Riverpod FutureProvider)
- referralInfoProvider — 推荐码信息
- referralListProvider — 直推列表首页
- pendingRewardsProvider — 待抵扣奖励
- allRewardsProvider — 完整奖励历史
### UI(referral_screen.dart,630行)
- _ReferralCodeCard:推荐码展示 + 一键复制 + 系统分享(Share.share)
- _StatsRow:3格统计卡(直推数 / 已激活 / 待抵扣积分)
- _RewardRulesCard:奖励规则说明卡片
- _ReferralPreviewList + _RewardPreviewList:首页预览 + "查看全部"导航
- _ReferralListPage + _RewardListPage:完整分页列表子页面
### 入口集成
- profile_page.dart:Billing 分组新增"邀请有礼"设置行(Gift 图标)
- app_router.dart:ShellRoute 内新增 /referral 路由 → ReferralScreen
## Web Admin (it0-web-admin/)
### 数据层
- `src/domain/entities/referral.ts`:TypeScript 接口定义(ReferralRelationship / ReferralReward / ReferralAdminStats / PaginatedResult<T>)
- `src/infrastructure/repositories/api-referral.repository.ts`:React Query 数据获取函数(getAdminReferralStats / listAdminRelationships / listAdminRewards)
### 管理页面 (src/app/(admin)/referral/page.tsx)
3 Tab 布局(概览 / 推荐关系 / 积分奖励):
- StatsOverview:3张统计卡(总推荐数 / 已激活 / 待领积分记录)
- RelationshipsTable:状态筛选下拉 + 分页表格(推荐人、被推荐人租户ID、推荐码、层级、状态、时间)
- RewardsTable:状态筛选下拉 + 分页表格(受益租户、金额、触发类型、状态、来源账单、时间)
- StatusBadge:彩色状态标签组件(PENDING/ACTIVE/REWARDED/EXPIRED/APPLIED)
### 导航集成
- sidebar.tsx:platformAdminItems 新增"推荐管理"(Gift 图标,/referral 路由)
- i18n/locales/zh/sidebar.json:新增 "referral": "推荐管理"
- i18n/locales/en/sidebar.json:新增 "referral": "Referrals"
## 部署说明
1. 服务器执行数据库迁移:
psql -U it0 -d it0 -f packages/shared/database/migrations/006-create-referral-tables.sql
2. 重建并启动新服务:
docker compose build referral-service api-gateway && docker compose up -d
3. 确认 .env 中设置 INTERNAL_API_KEY(服务间认证密钥)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 21:15:27 -08:00
hailin
8d2fd3335a
feat(telemetry): add presence-service + Flutter telemetry module
...
## Backend — packages/services/presence-service (新微服务)
完整的 DDD + Clean Architecture 实现,移植自 RWADurian presence-service,
针对 IT0 架构做了以下适配:
### 核心功能
- 心跳接口: POST /api/v1/presence/heartbeat(JWT 验证,60s 间隔)
→ Redis Sorted Set `presence:online_users` 记录在线时间戳
→ 默认 5 分钟窗口判断在线(PRESENCE_WINDOW_SECONDS=300)
- 事件上报: POST /api/v1/analytics/events(批量,最多 50 条)
→ 写入 presence_event_log 表 + 更新 presence_device_profile
→ Redis HyperLogLog `presence:dau:{date}` 实时 DAU 估算
- 查询接口(需 AdminGuard):
- GET /api/v1/analytics/online-count — 实时在线人数
- GET /api/v1/analytics/online-history — 历史在线快照
- GET /api/v1/analytics/dau — DAU 统计
### IT0 适配要点
- JWT payload: `sub` = UUID userId(非 RWADurian 的 userSerialNum)
→ JwtAuthGuard: request.user = { userId: payload.sub, roles, tenantId }
- AdminGuard: 改为检查 `roles.includes('admin')`(非 type==='admin')
- 移除 Kafka EventPublisherService(IT0 无 Kafka)
- 移除 Prometheus MetricsService(IT0 无 Prometheus)
- 表前缀改为 `presence_`(避免与其他服务冲突)
- userId 字段 VarChar(36)(UUID 格式,非原来的 VarChar(20))
- Redis DB=10 隔离(独立 key 空间)
### 数据库表(public schema)
- presence_event_log — 事件流水(append-only)
- presence_device_profile — 设备快照(upsert,每台设备一行)
- presence_daily_active_users — DAU 日统计
- presence_online_snapshots — 在线人数每分钟快照
### 定时任务(@nestjs/schedule)
- 每分钟: 采集在线人数快照 → presence_online_snapshots
- 每天 01:05 (UTC+8): 计算前一天 DAU → presence_daily_active_users
---
## Flutter — it0_app/lib/core/telemetry (新模块)
### 文件结构
- telemetry_service.dart — 单例入口,统筹所有组件
- models/telemetry_event.dart — 事件模型,toServerJson() 将设备字段提升为顶层列
- models/device_context.dart — 设备上下文(Android/iOS 信息)
- models/telemetry_config.dart — 远程配置(采样率/开关,支持远端同步)
- collectors/device_info_collector.dart — 采集 device_info_plus 设备信息
- storage/telemetry_storage.dart — SharedPreferences 队列(最多 500 条)
- uploader/telemetry_uploader.dart — 批量上传到 /api/v1/analytics/events
- session/session_manager.dart — WidgetsBindingObserver 监听前后台切换
- session/session_events.dart — 会话事件常量
- presence/heartbeat_service.dart — 定时心跳 POST /api/v1/presence/heartbeat
- presence/presence_config.dart — 心跳配置(间隔/requiresAuth)
- telemetry.dart — barrel 导出
### 集成点
- app_router.dart _tryRestore(): TelemetryService().initialize() 在 auth 之前
- auth_provider.dart login/loginWithOtp: setUserId + setAccessToken + resumeAfterLogin
- auth_provider.dart tryRestoreSession: 恢复 userId + accessToken
- auth_provider.dart logout: pauseForLogout + clearUserId + clearAccessToken
### 新增依赖
- device_info_plus: ^10.1.0
- equatable: ^2.0.5
---
## 基础设施
### Dockerfile.service
- 在 builder 和 production 阶段均添加 presence-service/package.json 的 COPY
### docker-compose.yml
- 新增 presence-service 容器(端口 3011/13011)
- DATABASE_URL: postgresql://... (Prisma 所需连接串格式)
- REDIS_HOST/PORT/DB: 10(presence 独立 Redis DB)
- APP_PORT=3011, JWT_SECRET, PRESENCE_WINDOW_SECONDS=300
- api-gateway depends_on 新增 presence-service
### kong.yml (dbless 声明式)
- 新增 presence-service 服务(http://presence-service:3011)
- presence-routes: /api/v1/presence
- analytics-routes: /api/v1/analytics
- 对整个 presence-service 启用 JWT 插件(Kong 层鉴权)
### DB 迁移
- packages/shared/database/src/migrations/010-create-presence-tables.sql
— 4 张 presence_ 前缀表 + 完整索引(IF NOT EXISTS 幂等)
- run-migrations.ts: runSharedSchema() 中新增执行 010-create-presence-tables.sql
---
## 部署步骤(服务器)
1. git pull
2. 执行 presence 表迁移(首次):
docker exec it0-postgres psql -U it0 -d it0 \
-f /path/to/010-create-presence-tables.sql
或通过 migration runner:
cd /home/ceshi/it0 && node packages/shared/database/dist/run-migrations.js
3. 重建并启动 presence-service:
docker compose build presence-service api-gateway
docker compose up -d presence-service api-gateway
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 17:44:01 -08:00
hailin
7d5840c245
feat(openclaw): Phase 1 — server pool + agent instance deployment infrastructure
...
## inventory-service
- New: pool_servers table (public schema, platform-admin managed)
- New: PoolServer entity, PoolServerRepository, PoolServerController
- CRUD endpoints at /api/v1/inventory/pool-servers
- Internal /deploy-creds endpoint (x-internal-api-key protected) for SSH key retrieval
- increment/decrement endpoints for capacity tracking
## agent-service
- New: agent_instances table (tenant schema)
- New: AgentInstance entity, AgentInstanceRepository, AgentInstanceController
- New: AgentInstanceDeployService — SSH-based docker deployment
- Queries pool server availability from inventory-service
- AES-256 encrypts OpenClaw gateway token at rest
- Allocates host ports in range 20000-29999
- Fires docker run for it0hub/openclaw-bridge:latest
- Async deploy with error capture
- Added ssh2 dependency for SSH execution
- Added INVENTORY_SERVICE_URL, INTERNAL_API_KEY, VAULT_MASTER_KEY to docker-compose
## openclaw-bridge (new package)
- packages/openclaw-bridge/ — custom Docker image
- Two processes via supervisord: OpenClaw gateway + IT0 Bridge (Node.js)
- IT0 Bridge exposes REST API on port 3000:
GET /health, GET /status, POST /task, GET /sessions, GET /metrics
- Connects to OpenClaw gateway at ws://127.0.0.1:18789 via WebSocket RPC
- Sends heartbeat to IT0 agent-service every 60s
- Dockerfile: multi-stage build (openclaw source + bridge TS compilation)
## Web Admin
- New: /server-pool page — list/add/edit/delete pool servers with capacity bars
- New: /openclaw-instances page — cross-tenant instance monitoring with status filter
- Sidebar: added 服务器池 (Database icon) + OpenClaw 实例 (Boxes icon) to platform_admin nav
## Flutter App
- my_agents_page: rewritten to show real AgentInstance data from /api/v1/agent/instances
- Added AgentInstance model with status-driven UI (running/deploying/stopped/error)
- Status badges with color coding + spinner for deploying state
- Summary chips showing running vs stopped counts
- api_endpoints.dart: added agentInstances endpoint
## Design docs
- OPENCLAW_INTEGRATION_PLAN.md: complete architecture document with all confirmed decisions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 11:11:21 -08:00
hailin
2182149c4c
feat(chat): voice-to-text fills input box instead of auto-sending
...
- Add POST /api/v1/agent/transcribe endpoint (STT only, no agent trigger)
- Add transcribeAudio() to chat datasource and provider
- VoiceMicButton now fills the text input field with transcript;
user reviews and sends manually
- Add OPENAI_API_KEY/OPENAI_BASE_URL to agent-service in docker-compose
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 07:01:39 -08:00
hailin
9ed80cd0bc
feat: implement complete commercial monetization loop (Phases 1-4)
...
## Phase 1 - Token Metering + Quota Enforcement
### Usage Tracking
- agent-service: add UsageRecord entity (per-tenant schema) tracking
inputTokens/outputTokens/costUsd per AI task
- Modify all 3 AI engines (claude-api, claude-code-cli, claude-agent-sdk)
to emit separate input/output token counts in the `completed` event
- claude-api-engine: costUsd = (input*3 + output*15) / 1,000,000
(claude-sonnet-4-5 pricing: $3/MTok in, $15/MTok out)
- agent.controller: persist UsageRecord and publish `usage.recorded`
event to Redis Streams on every task completion (non-blocking)
- shared/events: new events UsageRecordedEvent, SubscriptionChangedEvent,
QuotaExceededEvent, PaymentReceivedEvent
### Quota Enforcement
- TenantInfo: add maxServers, maxUsers, maxStandingOrders,
maxAgentTokensPerMonth fields
- TenantContextMiddleware: rewritten to query public.tenants table for
real quota values; 5-min in-memory cache; plan-based fallback on error
- TenantContextService: getTenant() returns null instead of throwing;
added getTenantOrThrow() for strict callers
- inventory-service/server.controller: 429 when maxServers exceeded
- ops-service/standing-order.controller: 429 when maxStandingOrders exceeded
- auth-service/auth.service: 429 when maxUsers exceeded
- 002-create-tenant-schema-template.sql: add usage_records table
## Phase 2 - billing-service (New Microservice, port 3010)
### Domain Layer (public schema, all UUIDs)
Entities: Plan, Subscription, Invoice, InvoiceItem, Payment, PaymentMethod,
UsageAggregate
Domain services:
- SubscriptionLifecycleService: full state machine (trialing -> active ->
past_due -> cancelled/expired); upgrades immediate, downgrades at period end
- InvoiceGeneratorService: monthly invoice = base fee + overage charges;
proration item for mid-cycle upgrades
- OverageCalculatorService: (totalTokens - includedTokens) * overageRate
### Infrastructure (all repos use DataSource directly, NOT TenantAwareRepository)
- PlanRepository, SubscriptionRepository, InvoiceRepository (atomic
transaction for invoice+items), PaymentRepository (payments + methods),
UsageAggregateRepository (UPSERT via ON CONFLICT for atomic accumulation)
### Application Use Cases
- CreateSubscriptionUseCase: called on tenant registration
- ChangePlanUseCase: upgrade (immediate + proration) or downgrade (scheduled)
- CancelSubscriptionUseCase: immediate or at-period-end
- GenerateMonthlyInvoiceUseCase: cron target (1st of month 00:05 UTC);
generates invoices, renews periods, applies scheduled downgrades
- AggregateUsageUseCase: Redis Streams consumer group billing-service,
upserts monthly usage aggregates from usage.recorded events
- CheckTokenQuotaUseCase: hard limit enforcement per plan
- CreatePaymentSessionUseCase + HandlePaymentWebhookUseCase
### REST API
- GET /api/v1/billing/plans
- GET/POST /api/v1/billing/subscription (+ /upgrade, /cancel)
- GET /api/v1/billing/invoices (paginated)
- GET /api/v1/billing/invoices/:id
- POST /api/v1/billing/invoices/:id/pay
- GET /api/v1/billing/usage/current + /history
- CRUD /api/v1/billing/payment-methods
- POST /api/v1/billing/webhooks/{stripe,alipay,wechat,crypto}
### Plan Seed (auto on startup via PlanSeedService)
- free: $0/mo, 100K tokens, no overage, hard limit 100%
- pro: $49.99/mo, 1M tokens, $8/MTok, hard limit 150%
- enterprise: $199.99/mo, 10M tokens, $5/MTok, no hard limit
## Phase 3 - Payment Provider Integration
### PaymentProviderRegistry (Strategy Pattern, mirrors EngineRegistry)
All providers use @Optional() injection; unconfigured providers omitted
- StripeProvider: PaymentIntent API; webhook via stripe.webhooks.constructEvent
- AlipayProvider: alipay-sdk; Native QR (precreate); RSA2 signature verify
- WeChatPayProvider: v3 REST; Native Pay code_url; AES-256-GCM decrypt;
HMAC-SHA256 request signing and webhook verification
- CryptoProvider: Coinbase Commerce; hosted checkout; HMAC-SHA256 verify
### WebhookController
All 4 webhook endpoints are public (no JWT) for payment provider callbacks.
rawBody: true enabled in main.ts for signature verification.
## Infrastructure Changes
- docker-compose.yml: billing-service container (port 13010);
added as dependency of api-gateway
- kong.yml: /api/v1/billing routes (JWT); /api/v1/billing/webhooks (public)
- 005-create-billing-tables.sql: 7 billing tables + invoice sequence +
ALTER tenants to add quota columns
- run-migrations.ts: 005 runs as part of shared schema step
## Phase 4 - Frontend
### Web Admin (Next.js)
New pages:
- /billing: subscription card + token usage bar + warning banner + invoices
- /billing/plans: comparison grid with USD/CNY toggle + upgrade/downgrade flow
- /billing/invoices: paginated table with Pay Now button
Sidebar: Billing group (CreditCard icon, 3 sub-items)
i18n: billing keys added to en + zh sidebar translations
### Flutter App
New feature module it0_app/lib/features/billing/:
- BillingOverviewPage: plan card + token LinearProgressIndicator +
latest invoice + upgrade button
- BillingProvider (FutureProvider): parallel fetch subscription/quota/invoice
Settings page: "订阅与用量" entry card
Router: /settings/billing sub-route
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 21:09:17 -08:00
hailin
f6dffe02c5
feat: add version-service for IT0 App version management
...
New NestJS microservice (port 3009) providing complete version management
API for IT0 App, designed to integrate with the existing mobile-upgrade
frontend (update.szaiai.com).
Backend — packages/services/version-service/ (9 new files):
- AppVersion entity: platform (ANDROID/IOS), versionName, buildNumber,
changelog, downloadUrl, fileSize, isForceUpdate, isEnabled, minOsVersion
- REST controller with 8 endpoints:
GET/POST /api/v1/versions — list (with platform/disabled filters) & create
GET/PUT/DELETE /api/v1/versions/:id — single CRUD
PATCH /api/v1/versions/:id/toggle — enable/disable
POST /api/v1/versions/upload — multipart APK/IPA upload (500MB limit)
POST /api/v1/versions/parse — extract version info from APK/IPA
- File storage: /data/versions/{platform}/ via Docker volume
- APK/IPA parsing: app-info-parser package
- Database: public.app_versions table (non-tenant, platform-level)
- No JWT auth (internal version management, consistent with existing apps)
Infrastructure changes:
- Dockerfile.service: added version-service package.json COPY lines
- docker-compose.yml: version-service container (13009:3009), version_data
volume, api-gateway depends_on
- kong.yml: version-service route (/api/v1/versions), CORS origin for
update.szaiai.com (mobile-upgrade frontend domain)
Deployment note: nginx needs /downloads/versions/ location + client_max_body_size 500m
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 07:48:31 -08:00
hailin
7fb0d1de95
refactor: remove Speechmatics STT integration entirely, default to OpenAI
...
- Delete speechmatics_stt.py plugin
- Remove speechmatics branch from voice-agent entrypoint
- Remove livekit-plugins-speechmatics dependency
- Change default stt_provider to 'openai' in entity, controller, and UI
- Remove SPEECHMATICS_API_KEY from docker-compose.yml
- Remove speechmatics option from web-admin settings dropdown
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 04:58:38 -08:00
hailin
f9c47de04b
feat: add STT provider switching (OpenAI ↔ Speechmatics) in settings
...
- Add VoiceConfig entity/repo/service/controller in agent-service
for per-tenant STT provider persistence (default: speechmatics)
- Add Speechmatics STT plugin in voice-agent with livekit-plugins-speechmatics
- Modify voice-agent entrypoint for 3-way STT selection:
metadata > agent-service config > env var fallback
- Add "Voice" section in web-admin settings page with STT provider dropdown
- Add i18n translations (en/zh) for voice settings
- Add SPEECHMATICS_API_KEY env var in docker-compose
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 22:13:18 -08:00
hailin
fb236de6e4
fix: set LiveKit node_ip to China IP for domestic WebRTC connectivity
...
LiveKit's use_external_ip auto-detected 154.84.135.121 (overseas) via
STUN, causing WebRTC ICE candidates to use an unreachable IP for
domestic mobile clients. Explicitly set node_ip to 14.215.128.96.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:51:17 -08:00
hailin
8a48e92970
fix: use domain names for API access, China IP for LiveKit
...
Flutter app now uses https://it0api.szaiai.com (nginx reverse proxy)
instead of direct IP:port. LiveKit URL uses China IP 14.215.128.96
for lower latency from domestic mobile clients.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:44:25 -08:00
hailin
7fb0168dc5
fix: keep voice-service on bridge networking to avoid port conflict
...
iconsulting-llm-gateway already occupies port 3008 on the host.
voice-service only has a single TCP port (no docker-proxy overhead),
so bridge networking with 13008:3008 mapping is sufficient.
Only livekit-server and voice-agent need host mode (UDP port ranges).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:23:13 -08:00
hailin
68ee2516d5
fix: use host networking for voice services to eliminate docker-proxy overhead
...
Bridge mode created 600+ docker-proxy processes for LiveKit's UDP port-range
mappings (30000-30100, 50000-50200). Switch livekit-server, voice-agent, and
voice-service to network_mode: host for zero-overhead networking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:58:32 -08:00
hailin
2dc361f7a0
chore: update docker-compose TTS defaults to gpt-4o-mini-tts
...
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 08:44:17 -08:00
hailin
cf60b8733f
fix: expose TURN relay ports for NAT traversal
...
Limit TURN relay range to 30000-30100 and expose via docker-compose.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 11:39:50 -08:00
hailin
2f0cb13ecb
fix: enable built-in TURN server for NAT traversal
...
Subscriber transport was timing out on DTLS handshake for clients
behind complex NAT (VPN/symmetric NAT). Enable LiveKit's built-in
TURN server on UDP port 3478.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 11:37:21 -08:00
hailin
2ce0e7cdd4
fix: use external LiveKit URL in voice-service config
...
The livekit_ws_url returned in token response needs to be the external
server address, not the internal Docker network name, so Flutter clients
can connect directly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 10:00:26 -08:00
hailin
94a14b3104
feat: migrate voice call from WebSocket/PCM to LiveKit WebRTC
...
实时语音对话架构迁移:WebSocket → LiveKit WebRTC
## 背景
原语音通话架构基于 FastAPI WebSocket 传输原始 PCM,管道串行执行
(VAD → 批量STT → Agent → 攒句 → 批量TTS),首音频延迟约 6 秒。
迁移到 LiveKit Agents 框架后,利用 WebRTC 传输 + 流水线并行,
预期延迟降至 1.5-2 秒。
## 架构
Flutter App ←── WebRTC (Opus/UDP) ──→ LiveKit Server ←──→ Voice Agent
livekit_client (自部署, Go) (Python, LiveKit Agents SDK)
├─ VAD (Silero)
├─ STT (faster-whisper / OpenAI)
├─ LLM (自定义插件 → agent-service)
└─ TTS (Kokoro / OpenAI)
关键设计:LLM 不直接调用 Claude API,而是通过自定义插件代理到现有
agent-service,保留 Tool Use、会话历史、租户隔离等能力。
## 新增服务
### voice-agent (packages/services/voice-agent/)
LiveKit Agent Worker,包含:
- agent.py: 入口,prewarm() 预加载模型,entrypoint() 编排会话
- plugins/agent_llm.py: 自定义 LLM 插件,代理 agent-service API
- POST /api/v1/agent/tasks 创建任务
- WS /ws/agent 订阅流式事件 (stream_event)
- 跨轮复用 session_id 保持对话上下文
- plugins/whisper_stt.py: 本地 faster-whisper STT (批量识别)
- plugins/kokoro_tts.py: 本地 Kokoro-82M TTS (24kHz PCM)
- config.py: pydantic-settings 配置
### LiveKit Server (deploy/docker/)
- livekit.yaml: 信令端口 7880, RTC TCP 7881, UDP 50000-50200
- docker-compose.yml: 新增 livekit-server + voice-agent 容器
### LiveKit Token 端点
- voice-service/src/api/livekit_token.py:
POST /api/v1/voice/livekit/token
生成 Room JWT,嵌入 auth_header 到 AgentDispatch metadata
## Flutter 客户端改造
- agent_call_page.dart: 从 ~814 行简化到 ~380 行
- 替换: WebSocketChannel, AudioRecorder, PcmPlayer, 手动心跳/重连
- 使用: Room.connect(), setMicrophoneEnabled(true), LiveKit 事件监听
- 波形动画改用 participant.audioLevel
- pubspec.yaml: 添加 livekit_client: ^2.3.0
- app_config.dart: 增加 livekitUrl 字段
- api_endpoints.dart: 增加 livekitToken 端点
## 配置说明 (环境变量)
- STT_PROVIDER: local (默认, faster-whisper) / openai
- TTS_PROVIDER: local (默认, Kokoro) / openai
- WHISPER_MODEL: base (默认) / small / medium / large
- WHISPER_LANGUAGE: zh (默认)
- KOKORO_VOICE: zf_xiaoxiao (默认)
- DEVICE: cpu (默认) / cuda
## 不变的部分
- agent-service: 完全不改,voice-agent 通过现有 API 调用
- voice-service 核心: pipeline/STT/TTS/VAD 保留 (Twilio 备用)
- Kong 网关: 现有路由不变
- 数据库: 无 schema 变更
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 08:55:33 -08:00
hailin
3ed20cdf08
refactor: clean up agent SSH setup after fixing host-local routing
...
- Remove iproute2/NET_ADMIN (no longer needed)
- Remove ip route hack from entrypoint.sh
- rwa-colocation-2 server record updated to use Docker gateway IP
since 14.215.128.96 is a host-local NIC on the IT0 server
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:11:44 -08:00
hailin
ae7d9251ec
fix: add route for host-local IP (14.215.128.96) in agent container
...
14.215.128.96 is bound to a host NIC (enp5s0) and unreachable from
Docker bridge via default NAT. Add NET_ADMIN + ip route via gateway.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:05:30 -08:00
hailin
0dea3f82bc
fix: mount correct SSH key (rwadurian_ed25519) in agent-service
...
The IT0 server has its own id_ed25519 which differs from the local
key that's authorized on RWADurian servers. Use a dedicated key file.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 13:05:01 -08:00
hailin
f0ad6e09e6
fix: move entrypoint.sh to project root (deploy/ is in .dockerignore)
...
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 12:14:31 -08:00
hailin
bad7f4802d
fix: use root entrypoint to copy SSH key then drop to appuser
...
The bind-mounted SSH key is owned by host uid (1000/node) but the
service runs as appuser (uid 1001). Use su-exec in entrypoint.sh
to copy the key as root, fix ownership, then drop privileges.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 12:13:55 -08:00
hailin
329916e1f6
fix: correct SSH key permissions in agent-service container
...
Mount host key to /tmp/host-ssh-key (read-only), then copy to
appuser's .ssh directory with correct ownership at container start.
Fixes "Permission denied" due to uid mismatch on bind mount.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 12:00:02 -08:00
hailin
795e8a11c5
feat: enable SSH access from agent-service container
...
- Add openssh-client to Dockerfile.service (alpine)
- Create .ssh directory with correct permissions for appuser
- Mount host SSH key into agent-service container (read-only)
This allows the Agent SDK to SSH into managed servers using the Bash tool.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 11:55:54 -08:00
hailin
cc0f06e2be
feat: SDK engine native resume with per-tenant HOME isolation
...
Replace prompt-prefix workaround with SDK's native resume mechanism.
Each tenant gets isolated HOME directory (/data/claude-tenants/{tenantId})
to prevent cross-tenant session file mixing. SDK session IDs are persisted
in session.metadata for cross-request resume support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 02:27:38 -08:00
hailin
c02c2a9a11
feat: add OpenAI TTS/STT provider support in voice pipeline
...
- Add STT_PROVIDER/TTS_PROVIDER config (local or openai) in settings
- Pipeline uses OpenAI API for STT/TTS when provider is "openai"
- Skip loading local models (Kokoro/faster-whisper) when using OpenAI
- VAD (Silero) always loads for speech detection
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:27:38 -08:00
hailin
d43baed3a5
feat: add OpenAI TTS/STT API endpoints for comparison testing
...
- Add openai package to voice-service requirements
- Add /api/v1/test/tts/synthesize-openai (tts-1/tts-1-hd/gpt-4o-mini-tts)
- Add /api/v1/test/stt/transcribe-openai (gpt-4o-transcribe/whisper-1)
- Add OPENAI_API_KEY and OPENAI_BASE_URL env vars to voice-service
- Flutter test page: SegmentedButton to toggle Local/OpenAI provider
- All endpoints maintain same response format for easy comparison
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 07:20:03 -08:00
hailin
7ac753ada4
fix: add ANTHROPIC_BASE_URL to agent-service for proxy access
...
The agent-service was missing the ANTHROPIC_BASE_URL environment variable,
causing the Claude Agent SDK to call api.anthropic.com directly instead of
going through the proxy at 67.223.119.33, resulting in 403 Forbidden errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 04:49:27 -08:00
hailin
6876ec569b
fix: remove ANTHROPIC_API_KEY from agent-service to use subscription mode
...
Default to OAuth subscription billing via ~/.claude/.credentials.json
instead of consuming API key credits.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:43:09 -08:00
hailin
82d12a5ff5
feat: mount voice model cache volumes to avoid re-downloading on restart
...
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 02:28:28 -08:00
hailin
abf5e29419
feat: route voice pipeline through agent-service instead of direct LLM
...
Voice calls now use the same agent task + WS subscription flow as the
chat UI, enabling tool use and command execution during voice sessions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 00:47:31 -08:00
hailin
d4391eef97
fix: run services as non-root user for SDK bypassPermissions
...
SDK blocks bypassPermissions when running as root for security.
Add non-root 'appuser' to Dockerfile.service and update volume
mounts to use /home/appuser/.claude paths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 06:41:10 -08:00
hailin
04a18a7899
fix: use acceptEdits mode and mount .claude.json for SDK
...
- bypassPermissions blocked by SDK when running as root
- Switch to acceptEdits with canUseTool for programmatic control
- Mount .claude.json config file into container
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 06:37:31 -08:00
hailin
3a6f9d9447
fix: mount .claude directory as read-write for SDK debug logs
...
SDK writes debug logs to ~/.claude/debug/ at runtime.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 06:21:31 -08:00
hailin
b963b7d4da
feat: enable SDK subscription mode with OAuth credentials mount
...
- Mount ~/.claude/ into agent-service container for OAuth token access
- Switch default engine to claude_agent_sdk
- Remove ANTHROPIC_API_KEY from env in subscription mode so SDK uses OAuth
- Keep API key mode for per-tenant billing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 06:14:45 -08:00
hailin
810dcd7def
feat: switch default engine to claude_api with base URL support
...
- Change AGENT_ENGINE_TYPE from claude_code_cli to claude_api in docker-compose
- Add ANTHROPIC_BASE_URL env var support to claude-api-engine
- Add ANTHROPIC_BASE_URL to agent-service environment in docker-compose
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 05:45:08 -08:00
hailin
9a1ecf10ec
fix: add restart policy, global error handlers, and fix tenant schema bug
...
- Add restart: unless-stopped to all 12 Docker services
- Add process.on(unhandledRejection/uncaughtException) to all 7 service main.ts
- Fix handleEventTrigger using tenantId UUID as schema name instead of slug lookup
- Wrap Redis event subscription callbacks in try/catch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 05:30:34 -08:00
hailin
48e47975ca
fix: configure Kong JWT auth flow with consumer credentials
...
- Add kid claim to auth-service JWT for Kong validation
- Add Kong consumer with JWT credential (shared secret via env)
- Add agent-config route to Kong for /api/v1/agent-config
- Kong Dockerfile uses entrypoint script to inject JWT_SECRET at runtime
- Fix frontend login path (/auth/login → /api/v1/auth/login)
- Extract tenantId from JWT on login and store as current_tenant
- Add auth guard in admin layout (redirect to /login if no token)
- Pass JWT_SECRET env var to Kong container in docker-compose
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 23:20:06 -08:00
hailin
e5dcfa6113
feat: configure it0.szaiai.com and it0api.szaiai.com domains
...
- Update Kong CORS origins to allow it0.szaiai.com
- Update WebSocket URL to wss://it0api.szaiai.com
- Fix proxy route to read API_BASE_URL at request time
(was being inlined at build time by Next.js standalone)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 22:54:17 -08:00
hailin
67d5a13c0c
fix: set compose project name to 'it0' for consistent image naming
...
Changes image names from docker-{service} to it0-{service}.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 02:57:42 -08:00
hailin
259838ae88
fix: set HOSTNAME=0.0.0.0 for Next.js standalone to bind all interfaces
...
Next.js standalone server binds to container hostname by default,
making it unreachable from 127.0.0.1 for healthchecks and from
Docker port forwarding.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 02:52:37 -08:00
hailin
83da374bbb
fix: use 127.0.0.1 in web-admin healthcheck to avoid IPv6 resolution
...
Node.js 18 resolves 'localhost' to ::1 (IPv6) but Next.js standalone
only binds to 0.0.0.0 (IPv4), causing Connection Refused.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 02:49:51 -08:00
hailin
3702fa3f52
fix: make voice-service startup graceful and fix device config
...
- Wrap model loading in try/except so server starts even if models fail
- Fix device env var mapping (unified 'device' field instead of 'whisper_device')
- Default Whisper model to 'base' instead of 'large-v3' (3GB) for CPU deployment
- Increase healthcheck start_period to 120s for model download time
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 00:20:12 -08:00
hailin
d0447fb69f
fix: use node/python HTTP healthchecks instead of wget
...
wget returns error on 404, but services are healthy (just no root
endpoint). Using node http.get for NestJS services (accepts any
non-5xx response) and python urllib for voice-service.
Also upgraded api-gateway depends_on to service_healthy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 00:13:47 -08:00
hailin
e7ae82e51d
feat: add healthcheck to all services in docker-compose
...
NestJS services use wget to check API endpoints.
voice-service uses curl to check FastAPI /docs endpoint.
web-admin uses wget to check Next.js root.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 00:10:38 -08:00
hailin
4db373b03f
.
2026-02-19 20:37:19 +08:00
hailin
e875cd49bb
fix: resolve Kong image tag and port conflicts for shared server
...
- Change Kong base image from kong:3.7-alpine (non-existent) to kong:3.7
- Remap all host ports to avoid conflicts with existing iconsulting services:
- Backend services: 13001-13008 (was 3001-3008)
- Web admin: 13000 (was 3000)
- API gateway: 18000/18001 (was 8000/8001)
- PostgreSQL: 15432 (was 5432)
- Redis: 16379 (was 6379)
- Add container_name with it0- prefix to all services
- Update deploy.sh health check ports to match new mappings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 04:36:23 -08:00
hailin
9120f4927e
fix: add Dockerfiles and fix docker-compose build configuration
...
- Add shared Dockerfile.service for all 7 NestJS microservices using
multi-stage build with pnpm workspace support
- Add Dockerfile for web-admin (Next.js standalone output)
- Add .dockerignore files for root and web-admin
- Fix docker-compose.yml: use monorepo root as build context with
SERVICE_NAME build arg instead of per-service Dockerfiles
- Fix postgres/redis missing network config (services couldn't reach them)
- Use .env variables for DB credentials instead of hardcoded values
- Add JWT_REFRESH_SECRET and REDIS_URL to services that were missing them
- Add DB init script volume mount for postgres
- Remove deprecated version: '3.8' from all compose files
- Add output: 'standalone' to next.config.js for optimized Docker builds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 04:31:23 -08:00
hailin
e761b65b6e
feat: add deployment scripts with SSL support for production
...
Backend deploy script (deploy/docker/deploy.sh):
- install: auto-generate .env with secure secrets (JWT, DB passwords, vault keys)
- up/down/restart: manage all services (infra + app + gateway)
- build/build-no-cache: Docker image management
- status/health: health checks for all 9 services + infrastructure
- migrate: TypeORM migration commands (run/generate/revert/schema-sync)
- infra-*: standalone infrastructure management (PostgreSQL + Redis)
- voice-*: voice service with GPU support (docker-compose.voice.yml overlay)
- start-svc/stop-svc/rebuild-svc: individual service operations
- ssl-init: obtain Let's Encrypt certificates for both domains independently
- ssl-up/ssl-down: start/stop with Nginx SSL reverse proxy
- ssl-renew/ssl-status: certificate renewal and status checks
Web Admin deploy script (it0-web-admin/deploy.sh):
- build/start/stop/restart/logs/status/clean commands
- auto-generates Dockerfile (Next.js multi-stage standalone build)
- auto-generates docker-compose.yml
- configurable API domain (default: it0api.szaiai.com)
SSL / Nginx configuration:
- nginx.conf: reverse proxy for both domains with HTTP->HTTPS redirect
- it0api.szaiai.com -> api-gateway:8000 (with WebSocket support)
- it0.szaiai.com -> web-admin:3000 (with Next.js HMR support)
- nginx-init.conf: HTTP-only config for initial ACME challenge verification
- ssl-params.conf: TLS 1.2/1.3, HSTS, security headers (Mozilla Intermediate)
- docker-compose.ssl.yml: Nginx + Certbot overlay with auto-renewal (12h cycle)
Domain plan:
- https://it0api.szaiai.com — API endpoint (backend services)
- https://it0.szaiai.com — Web Admin dashboard (frontend)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 17:44:27 -08:00