Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
eb5ff8e
Add shared ASR provider layer
JunchenMeteor May 31, 2026
0df68b6
Add Xunfei ASR bootstrap and API guard
JunchenMeteor May 31, 2026
fabee3c
Require auth for protected API routes
JunchenMeteor May 31, 2026
9e5bbe2
Merge pull request #219 from JunchenMeteor/dev/feature/shared-asr-pro…
JunchenMeteor May 31, 2026
3627899
Add ASR bootstrap diagnostics
JunchenMeteor May 31, 2026
9cf2cb7
Merge pull request #221 from JunchenMeteor/dev/feature/asr-runtime-in…
JunchenMeteor May 31, 2026
53efb95
Document native PCM ASR plan
JunchenMeteor May 31, 2026
594b489
Add native PCM ASR diagnostics
JunchenMeteor May 31, 2026
58001bb
Merge pull request #223 from JunchenMeteor/dev/mobile/native-pcm-asr-…
JunchenMeteor May 31, 2026
ac88658
Fix request errors and history loading
JunchenMeteor Jun 1, 2026
c38b91e
Merge pull request #225 from JunchenMeteor/dev/fix/asr-diagnostic-aut…
JunchenMeteor Jun 1, 2026
b8cf462
[Mobile] Add ASR P4 evaluation diagnostics (#227)
JunchenMeteor Jun 1, 2026
e756319
Integrate Xunfei ASR into mobile sessions (#229)
JunchenMeteor Jun 1, 2026
18235c0
Normalize deployment env ownership and Tencent workflow (#231)
JunchenMeteor Jun 2, 2026
74c3cb4
Add retry handling to Tencent deploy workflow (#233)
JunchenMeteor Jun 2, 2026
d583d92
Fix Tencent deploy branch expansion (#235)
JunchenMeteor Jun 2, 2026
411672d
Keep Tencent PM2 deployment alive (#237)
JunchenMeteor Jun 2, 2026
afedc60
Support client session IDs on HTTP (#239)
JunchenMeteor Jun 3, 2026
e00d3dc
Correct Xunfei voice profile genders (#241)
JunchenMeteor Jun 3, 2026
a385939
Harden mobile preference auth (#243)
JunchenMeteor Jun 3, 2026
bbacddd
Retry mobile settings data after auth (#245)
JunchenMeteor Jun 3, 2026
6f25dc2
Fix iOS ATS HTTP exception (#247)
JunchenMeteor Jun 3, 2026
feb2db6
Stabilize settings sync and app feedback
JunchenMeteor Jun 3, 2026
1abc347
Stop feedback loading loops
JunchenMeteor Jun 3, 2026
b87f203
Aggregate mobile settings loading
JunchenMeteor Jun 3, 2026
b4625ad
Add shared app operation groups
JunchenMeteor Jun 3, 2026
0bb0fc7
Respect selected response language
JunchenMeteor Jun 3, 2026
5d5261b
Apply settings updates without full reload (#259)
JunchenMeteor Jun 3, 2026
5d18283
Document runtime feedback and settings sync (#261)
JunchenMeteor Jun 3, 2026
295f898
Restore Xunfei STT submission and locale persistence (#263)
JunchenMeteor Jun 3, 2026
b3e4297
Prevent first utterance loss and route Xunfei voices (#265)
JunchenMeteor Jun 4, 2026
3d46b1a
Ensure session start readiness and stop playback (#267)
JunchenMeteor Jun 4, 2026
97a099d
Recover Xunfei PCM capture when frames stop (#269)
JunchenMeteor Jun 4, 2026
2cb086d
Stabilize mobile session teardown and auth feedback (#272)
JunchenMeteor Jun 4, 2026
50ce734
Guard stale Xunfei starts after route changes (#274)
JunchenMeteor Jun 4, 2026
cf36360
Align security workflow permissions
JunchenMeteor Jun 5, 2026
e3c9e3e
Fix security scan alerts
JunchenMeteor Jun 5, 2026
20058a2
Use secure accent randomness
JunchenMeteor Jun 5, 2026
529aa26
Fix mobile API domains and timeouts
JunchenMeteor Jun 6, 2026
a1ced53
Merge pull request #282 from JunchenMeteor/dev/fix/mobile-api-domain-…
JunchenMeteor Jun 6, 2026
552dad2
Prepare v1.3.0 release
JunchenMeteor Jun 6, 2026
6a82b17
Merge pull request #284 from JunchenMeteor/dev/release/v1.3.0-prep
JunchenMeteor Jun 6, 2026
2a17061
Fix Tencent deploy ports
JunchenMeteor Jun 6, 2026
56c2589
Merge pull request #288 from JunchenMeteor/dev/fix/tencent-deploy-ports
JunchenMeteor Jun 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 0 additions & 32 deletions .env.local.example

This file was deleted.

3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: CI

permissions:
contents: read

on:
pull_request:
push:
Expand Down
72 changes: 72 additions & 0 deletions .github/workflows/deploy-tencent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Deploy Tencent

permissions:
contents: read

on:
push:
branches:
- main
- release
workflow_dispatch:

concurrency:
group: deploy-tencent-${{ github.ref }}
cancel-in-progress: false

jobs:
deploy:
name: Deploy Web/API
runs-on: [self-hosted, linux, x64, tencent, meteorvoice]
environment: tencent
steps:
- name: Resolve target
id: target
shell: bash
run: |
if [ "${GITHUB_REF_NAME}" = "release" ]; then
echo "app_dir=/srv/meteorvoice-release" >> "$GITHUB_OUTPUT"
echo "pm2_name=meteorvoice-release" >> "$GITHUB_OUTPUT"
echo "port=3100" >> "$GITHUB_OUTPUT"
else
echo "app_dir=/srv/meteorvoice" >> "$GITHUB_OUTPUT"
echo "pm2_name=meteorvoice" >> "$GITHUB_OUTPUT"
echo "port=3101" >> "$GITHUB_OUTPUT"
fi

- name: Deploy
shell: bash
run: |
retry() {
local attempts="$1"
local delay="$2"
shift 2

local attempt=1
until "$@"; do
if [ "$attempt" -ge "$attempts" ]; then
return 1
fi

echo "Command failed. Retrying in ${delay}s (${attempt}/${attempts})..."
sleep "$delay"
attempt=$((attempt + 1))
done
}

cd '${{ steps.target.outputs.app_dir }}'
retry 5 15 timeout 180s git fetch origin "$GITHUB_REF_NAME"
git checkout "$GITHUB_REF_NAME"
git reset --hard "origin/$GITHUB_REF_NAME"
retry 3 15 npm ci
set -a
. /etc/meteorvoice/meteorvoice.env
set +a
retry 2 15 npm run build
unset RUNNER_TRACKING_ID
PORT='${{ steps.target.outputs.port }}' HOSTNAME=127.0.0.1 \
pm2 restart '${{ steps.target.outputs.pm2_name }}' --update-env || \
PORT='${{ steps.target.outputs.port }}' HOSTNAME=127.0.0.1 \
pm2 start npm --name '${{ steps.target.outputs.pm2_name }}' -- run start --workspace @meteorvoice/web -- --hostname 127.0.0.1 --port '${{ steps.target.outputs.port }}'
pm2 save
retry 12 5 curl -fsS 'http://127.0.0.1:${{ steps.target.outputs.port }}/' >/dev/null
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ All spec, plan, and implementation handoff files live in `docs/`:

## Environment

- `.env.local` — local dev credentials (never committed)
- `.env.local.example` — template
- Vercel env vars: `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`, `DEEPSEEK_API_KEY`, `DEEPSEEK_BASE_URL`
- `apps/web/.env.local` — Web/API local development credentials (never committed)
- `apps/web/.env.local.example` — Web/API template
- `apps/mobile/.env` — mobile build-time public variables only, such as `EXPO_PUBLIC_SUPABASE_URL` and `EXPO_PUBLIC_SUPABASE_ANON_KEY`
- `/etc/meteorvoice/meteorvoice.env` — Tencent/server runtime environment for the deployed Web/API process
- Vercel env vars mirror the Web/API server environment: `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`, `DEEPSEEK_API_KEY`, `DEEPSEEK_BASE_URL`
53 changes: 43 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</p>

<p align="center">
<img alt="Next.js" src="https://img.shields.io/badge/Next.js-15-black?style=for-the-badge&logo=nextdotjs" />
<img alt="Next.js" src="https://img.shields.io/badge/Next.js-16-black?style=for-the-badge&logo=nextdotjs" />
<img alt="Expo" src="https://img.shields.io/badge/Expo-55-000020?style=for-the-badge&logo=expo&logoColor=white" />
<img alt="Supabase" src="https://img.shields.io/badge/Supabase-PostgreSQL-3ECF8E?style=for-the-badge&logo=supabase&logoColor=white" />
<img alt="AI" src="https://img.shields.io/badge/AI-DeepSeek-4B7BFF?style=for-the-badge" />
Expand Down Expand Up @@ -72,8 +72,12 @@ sequenceDiagram
- Accent adaptation across sessions (American, British, Indian, Australian, and more)
- Non-blocking correction panel — corrections appear without interrupting the conversation
- Bilingual UI (English and Chinese) outside the conversation area
- Response language routing — AI replies and correction explanations follow the selected UI language
- Theme switching with CSS custom properties
- Login, session history, and preference sync through Supabase
- Shared ASR provider layer with Xunfei bootstrap, native iOS PCM capture, and provider diagnostics
- Unified runtime feedback for loading, blocking operations, network errors, and 401 sign-out
- Authenticated API guard for high-cost AI, TTS, ASR, and session routes
- Mock AI/STT/TTS providers for local development without API keys
- Native mobile client (iOS/Android) via Expo React Native

Expand All @@ -85,16 +89,18 @@ flowchart TB
Mobile[MeteorVoice Mobile<br/>Expo React Native]

subgraph Platform[Backend]
API[Next.js API Routes<br/>TTS / Chat / Session / Turns]
API[Next.js API Routes<br/>TTS / ASR / Chat / Session / Turns]
Supabase[Supabase<br/>Auth / DB / Preferences]
AI[AI Provider<br/>DeepSeek via Vercel AI SDK]
TTS[TTS Providers<br/>Xunfei / Volcengine / Tencent / Azure]
ASR[ASR Providers<br/>Native / Xunfei / Azure-ready]
end

subgraph Shared[packages/]
SharedPkg[shared<br/>types / i18n / provider interfaces]
SessionCore[session-core<br/>turn lifecycle / workflow]
APIClient[api-client<br/>typed API calls]
Runtime[shared runtime<br/>feedback / operation groups]
end

Web --> API
Expand All @@ -103,17 +109,26 @@ flowchart TB
API --> Supabase
API --> AI
API --> TTS
API --> ASR
Web --> Shared
Mobile --> Shared
Web --> Runtime
Mobile --> Runtime
```

Responsibility boundaries:

- `apps/web`: Next.js full-stack app — UI, API routes, server-side TTS/AI orchestration.
- `apps/mobile`: Expo React Native native client — voice session UI, native audio, background keep-alive.
- `packages/shared`: cross-client types, i18n strings, provider interfaces, TTS capabilities.
- `apps/mobile`: Expo React Native native client — voice session UI, native audio, native PCM capture, background keep-alive.
- `packages/shared`: cross-client types, i18n strings, provider interfaces, ASR/TTS capabilities, app feedback state, and grouped async operation helpers.
- `packages/session-core`: platform-neutral turn lifecycle and workflow state machine.
- `packages/api-client`: typed API calls shared by Web and Mobile.
- `packages/api-client`: typed API calls shared by Web and Mobile, including request timeout and formatted error handling.

Settings synchronization now separates full refresh and targeted updates:

- Page entry, login, foreground resume, and manual refresh use grouped operations so multiple API requests share one loading state.
- A single setting save uses the returned `/api/preferences` payload and applies only the affected settings domain.
- AI response language is passed as `responseLocale`; ASR recognition language is configured separately.

## Project Structure

Expand Down Expand Up @@ -171,12 +186,15 @@ SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
DEEPSEEK_API_KEY=your-deepseek-api-key # optional — mock AI works without it
```

TTS provider keys (all optional — mock TTS works without them):
ASR/TTS provider keys (all optional — mock/native fallback works without them):

```text
ASR_PROVIDER=native
TTS_PROVIDER=mock
XUNFEI_APP_ID=
XUNFEI_API_KEY=
XUNFEI_API_SECRET=
XUNFEI_ASR_PRODUCT=zh_iat
XUNFEI_TTS_VOICE= # default fallback vcn; coach voice is selectable in Settings
VOLCENGINE_ACCESS_KEY=
VOLCENGINE_SECRET_KEY=
Expand All @@ -202,7 +220,7 @@ npm run dev

Open `http://127.0.0.1:3001`

The app runs fully in mock mode without any API keys. Real AI replies require `DEEPSEEK_API_KEY`. Real voice output requires at least one TTS provider key.
The app runs fully in mock mode without any API keys. Real AI replies require `DEEPSEEK_API_KEY`. Real voice output requires at least one TTS provider key. Remote ASR requires provider credentials and `ASR_PROVIDER`; otherwise mobile can use native speech recognition.

## Mobile

Expand Down Expand Up @@ -233,7 +251,7 @@ Set the API base URL in `apps/mobile/app.json`:
```json
"extra": {
"apiBaseUrl": "https://meteorvoice.jcmeteor.com",
"apiBaseUrlPreview": "https://meteorvoice-pre.jcmeteor.com"
"apiBaseUrlPreview": "https://mv-pre.jcmeteor.com"
}
```

Expand All @@ -253,6 +271,20 @@ Each user's selected provider is stored in Supabase. Provider credentials stay i

See `docs/tts-integration.md` for provider setup details.

## ASR Providers

MeteorVoice now exposes a shared ASR provider layer:

| Provider | Key | Status |
|----------|-----|--------|
| Native mobile speech | `native` | Default fallback |
| Xunfei `zh_iat` | `xunfei` | Signed WebSocket bootstrap and iOS PCM streaming path |
| Azure Speech | `azure` | Contract-ready, pending runtime adapter |

The ASR layer is split from AI response language routing: `ASR languageMode` controls recognition, while `responseLocale` controls how the coach replies.

See `docs/asr-provider-layer.md` for contracts, rollout steps, diagnostics, and test guidance.

## Validation

```bash
Expand Down Expand Up @@ -280,6 +312,7 @@ flowchart LR
| **Dual-platform architecture** | `apps/web` + `apps/mobile` + `packages/*` monorepo | ✅ Delivered |
| **Immersive UI** | Voice waveform, desktop/mobile layouts, real-time subtitles | ✅ Delivered |
| **Productization** | History with expansion/pagination, cross-device preferences sync, CI parallel jobs, mobile audio hardening | ✅ Delivered |
| **Semantic endpointing** | L1 local rules + L2 LLM-based end-of-turn detection + L3 safety net, replacing fixed silence timeout | 🚧 In implementation |
| **Accent capabilities** | TTS provider voice catalog, multi-accent speakers (US/UK/Indian/Australian) | 📋 Planned |
| **Semantic endpointing** | L1 local rules + L2 LLM-based end-of-turn detection + L3 safety net, replacing fixed silence timeout | ✅ Delivered |
| **ASR provider layer** | Shared ASR contracts, Xunfei bootstrap, native PCM diagnostics, mobile session integration | ✅ Delivered |
| **Accent capabilities** | TTS provider voice catalog, multi-accent speakers (US/UK/Indian/Australian) | 🚧 In implementation |
| **Distribution** | TestFlight beta, EAS production build | 📋 Planned |
Loading
Loading