You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
|
|
5 hours ago | |
|---|---|---|
| app | 5 hours ago | |
| audio | 5 hours ago | |
| content | 7 hours ago | |
| server | 6 hours ago | |
| tools | 5 hours ago | |
| .env.example | 7 hours ago | |
| .gitignore | 7 hours ago | |
| README.md | 5 hours ago | |
| SPEC.md | 7 hours ago | |
| package-lock.json | 7 hours ago | |
| package.json | 7 hours ago | |
README.md
🇮🇹 Italiano Vivo
App de italiano situacional-first com voz livre e scoring de inteligibilidade. Aprenda como uma criança: errando mas se comunicando.
Visão
- Scoring em 3 eixos: Sentido transmitido / Naturalidade / Precisão gramatical
- Tolerância estilo criança: erro gramatical que não impede entendimento = sucesso comunicativo
- Voz livre: grave, receba feedback estruturado, nunca "Errado"
- Salvavidas (Salvavita): treine estratégias de comunicação quando a palavra some
- SRS de chunks: revisão espaçada de frases, não palavras isoladas
- PWA offline: catálogo, áudio e SRS funcionam sem conexão
- CEFR can-do: badges baseados em competências reais ("você consegue reclamar de um item com defeito")
Como Rodar
Pré-requisitos
- Node.js ≥ 20
- LiteLLM proxy rodando (ou configure
LITELLM_BASE_URLpara outro endpoint OpenAI-compatible)
Instalação
# Instalar dependências do monorepo
npm install
# Configurar variáveis de ambiente
# rode SEMPRE a partir de server/ (o dotenv le server/.env pelo cwd):
cd server && npm run dev # ou node dist/index.js
# Edite server/.env com sua chave da API
Desenvolvimento
# Roda app + server simultaneamente
npm run dev
- App: http://localhost:5173
- Server: http://localhost:8787
- Health check: http://localhost:8787/health
Build
npm run build # Build server + app
npm run typecheck # Verificação de tipos
Endpoints do Server
| Método | Rota | Descrição |
|---|---|---|
| GET | /health |
Status do servidor |
| POST | /api/judge |
Avalia fala do aluno em 3 eixos (sentido, naturalidade, gramática) |
| POST | /api/transcribe |
Transcreve áudio via modelo omni (ASR tolerante a sotaque) |
| GET | /api/tts-proxy?text=...&voice=elsa|diego |
Serve áudio TTS pré-gerado do cache local |
POST /api/judge
Entrada:
{
"missionId": "bambino-m01",
"scenario": "Al bar",
"learnerGoalPt": "Pedir um café",
"expectedIt": "Vorrei un caffè, per favore",
"utteranceText": "io volere caffe",
"transcriptRaw": "io volere caffe",
"audioBase64": "..."
}
Saída:
{
"meaning_conveyed": "full",
"naturalness": 45,
"natural_version": "Vorrei un caffè, per favore",
"grammar": [
{
"it_said": "io volere",
"problem_pt": "Infinitivo em vez de conjugação",
"fix_it": "vorrei / voglio",
"severity": "minor"
}
],
"next_move": {
"italian": "Certo! Un caffè. Lo vuole al banco o al tavolo?",
"portuguese": "Claro! Um café. Quer no balcão ou na mesa?"
},
"praise_or_push": "Quase lá! Um italiano diria: Vorrei un caffè, per favore"
}
Estrutura de Pastas
cursoitaliano/
├── app/ # PWA React + Vite + TypeScript
│ ├── public/ # manifest.webmanifest, ícones
│ └── src/
│ ├── db/db.ts # IndexedDB via Dexie
│ ├── engine/srs.ts # SM-2 simplificado
│ ├── types/content.ts # Tipos do conteúdo JSON
│ ├── App.tsx # Componente principal
│ ├── main.tsx # Entry point
│ └── styles.css # Design system
├── server/ # Node + Express + TypeScript
│ ├── cache/audio/ # Áudios TTS pré-gerados (offline)
│ └── src/
│ ├── lib/llm.ts # Cliente LiteLLM singleton
│ ├── routes/
│ │ ├── judge.ts # Motor de feedback 3 eixos
│ │ ├── transcribe.ts # ASR via omni model
│ │ └── tts.ts # Proxy de áudio TTS
│ └── index.ts # Express app
├── content/ # JSONs versionados (gerados por pipeline)
├── audio/ # Assets de áudio por missão
├── SPEC.md # Especificação completa do produto
├── .env.example # Template de variáveis de ambiente
└── package.json # Monorepo root (npm workspaces)
Dois ritmos de áudio 🐢🐇
Cada frase do catálogo possui dois arquivos de áudio gerados com velocidades de fala reais (não playbackRate):
<key>.mp3— velocidade natural (padrão do TTS)<key>--slow.mp3— fala desacelerada em -30% viaedge-tts --rate=-30%
Por que rate real e não playback?
Crianças aprendem ouvindo devagar de verdade: a prosódia, entonação e pausas naturais são preservadas quando o TTS gera a fala mais lenta desde a síntese. Playback acelerado/desacelerado distorce formantes e perde a cadência natural da língua.
Regerar áudios
# Gerar apenas variantes lentas (idempotente, pula >500 bytes)
node tools/gen-audio.mjs --only slow
# Gerar apenas variantes rápidas/naturais
node tools/gen-audio.mjs --only fast
# Gerar ambas (default)
node tools/gen-audio.mjs
No player da app, dois botões lado a lado: 🐢 lento e 🐇 naturale. Se o arquivo lento falhar (404), fallback silencioso para o natural.
Modelos Utilizados
| Modelo | Uso | Variável |
|---|---|---|
| qwen3.8-max | Juiz de feedback, geração de missões | CHAT_MODEL |
| qwen3.8-flash | Drills rápidos, fallback de quota | FAST_MODEL |
| qwen3-omni-flash | Transcrição de áudio (ASR) | OMNI_MODEL |
Todos acessados via LiteLLM proxy (LITELLM_BASE_URL).
Roadmap
- Esqueleto do monorepo (app PWA + server LiteLLM)
- Geração de conteúdo: Trilha Bambino (B1-B12) + Viaggio (V1-V6) = 18 missões
- Pipeline de geração de áudio TTS (edge-tts) — ~479 arquivos mp3
- Judge 3-eixos CEFR com tolerância estilo criança
- SRS de chunks com Dexie/IndexedDB
- Service Worker para modo offline completo
- Minijogo "descreva sem nomear" (circunlóquio)
- Modo carro/shadowing com medição de lag
- Trilhas restantes (Viaggio M7–M15, Vivere completa)
- Preparatório CILS/CELI B1 cidadania
- Wrapper Android (Capacitor)
- Calibração do judge com votos "isso estava certo"
Licença
Privado — uso pessoal.