Switch coach experience to English only

This commit is contained in:
Ismail Ali
2026-04-28 15:35:11 +02:00
parent 63d48a5b59
commit 82d982dea0
11 changed files with 73 additions and 71 deletions

View File

@@ -1,19 +1,19 @@
# English AI Coach
English AI Coach ist ein Starterprojekt fuer eine mobile App zum Englischlernen mit KI. Die App hilft Nutzern, kurze englische Saetze zu schreiben, Fehler zu erkennen und Korrekturen mit deutscher Erklaerung zu erhalten.
English AI Coach is a starter project for a mobile AI-based English learning app. It helps users write short English sentences, find mistakes, and get clear corrections in English.
Das Projekt ist bewusst schlank gehalten: React Native, Expo, TypeScript und React Navigation. Es nutzt keine unnoetigen Expo-only Libraries und ist so vorbereitet, dass spaeter lokale KI ueber Ollama, Speech-to-Text, Text-to-Speech und Push Notifications ergaenzt werden koennen.
The project is intentionally lightweight: React Native, Expo, TypeScript, and React Navigation. It avoids unnecessary Expo-only libraries and is prepared for local AI with Ollama, speech input, text-to-speech, and push notifications.
## Features MVP
- HomeScreen mit Fortschrittskarte und Mock Daily Reminder
- Level-Auswahl fuer A1, A2, B1 und B2
- ChatScreen mit einfacher User- und AI-Nachrichtenansicht
- AI-Antworten koennen auf dem iPhone vorgelesen werden
- Mock KI-Service fuer Beispielkorrekturen
- TypeScript-typisierte Navigation
- Struktur fuer spaetere Services, Hooks und Utilities
- Web/PWA-Ausbau vorbereitet ueber Expo Web
- HomeScreen with progress card and mock daily reminder
- Level selection for A1, A2, B1, and B2
- ChatScreen with user and AI messages
- AI replies can be read aloud on iPhone
- Ollama integration with offline fallback examples
- TypeScript-typed navigation
- Structure for future services, hooks, and utilities
- Web/PWA path prepared through Expo Web
## Beispiel
@@ -22,7 +22,7 @@ User: I go yesterday to work
AI Coach:
Good try. Correct: "I went to work yesterday."
"Yesterday" braucht Past Tense.
Use the past tense with "yesterday".
```
## Tech Stack
@@ -31,11 +31,11 @@ Good try. Correct: "I went to work yesterday."
- Expo
- TypeScript
- React Navigation
- Geplant: Ollama API fuer lokale KI
- Geplant: Speech-to-Text und Text-to-Speech
- Text-to-Speech mit `expo-speech`
- Spracheingabe aktuell ueber iPhone-Diktierfunktion in der Tastatur
- Geplant: Push Notifications fuer Daily Reminder
- Ollama API for local AI
- Text-to-speech with `expo-speech`
- Speech input currently through iPhone keyboard dictation
- Planned: in-app speech-to-text
- Planned: push notifications for daily reminders
## Projektstruktur
@@ -69,56 +69,56 @@ english-ai-coach/
## Installation
Voraussetzungen:
Requirements:
- Node.js
- npm
- Expo ueber `npx expo`
- Expo through `npx expo`
Abhaengigkeiten installieren:
Install dependencies:
```bash
npm install
```
Optionale lokale KI-Konfiguration anlegen:
Create optional local AI configuration:
```bash
cp .env.example .env
```
In `.env` kannst du dein Ollama-Modell eintragen:
Set your Ollama model in `.env`:
```text
EXPO_PUBLIC_OLLAMA_BASE_URL=http://localhost:11434
EXPO_PUBLIC_OLLAMA_MODEL=llama3.2
```
Wenn du die App auf einem echten Smartphone mit Expo Go testest, ist `localhost` das Smartphone selbst. Nutze dann die IP deines PCs, zum Beispiel:
When testing on a real phone with Expo Go, `localhost` means the phone itself. Use your laptop IP instead, for example:
```text
EXPO_PUBLIC_OLLAMA_BASE_URL=http://192.168.10.102:11434
EXPO_PUBLIC_OLLAMA_MODEL=qwen2.5:7b
```
App starten:
Start the app:
```bash
npm run start
```
## Spracheingabe und Vorlesen
## Speech Input And Playback
Vorlesen funktioniert direkt in Expo Go. AI-Antworten werden automatisch ueber den iPhone-Lautsprecher vorgelesen. Im Chat gibt es ausserdem Buttons fuer `Letzte Antwort vorlesen` und `Stop`.
Playback works directly in Expo Go. AI replies are read aloud through the iPhone speaker. The chat also has buttons for `Read last answer` and `Stop`.
Spracheingabe funktioniert aktuell ueber die iPhone-Tastatur:
Speech input currently works through the iPhone keyboard:
- Chat-Eingabefeld antippen
- Mikrofon-Symbol auf der iPhone-Tastatur druecken
- Englisch sprechen
- Text pruefen und senden
- Tap the chat input field
- Press the microphone icon on the iPhone keyboard
- Speak English
- Review the text and send it
Echtes In-App Speech-to-Text mit eigenem Mikrofon-Button ist fuer spaeter geplant. Dafuer braucht die App entweder ein natives iOS-Modul nach `expo prebuild` oder eine Transkriptions-API wie Whisper.
Real in-app speech-to-text with a custom microphone button is planned for later. That requires either a native iOS module after `expo prebuild` or a transcription API such as Whisper.
iOS Preview starten:
@@ -132,23 +132,23 @@ Web Preview starten:
npm run web
```
Hinweis: Fuer lokale iOS-Simulator-Builds wird macOS mit Xcode benoetigt. Mit Expo Go kann die App auf einem echten Geraet getestet werden.
Note: local iOS simulator builds require macOS with Xcode. With Expo Go, the app can be tested on a real device.
## Ollama Integration
Der Chat nutzt `src/services/ollamaClient.ts` fuer echte lokale KI-Antworten. Falls Ollama nicht erreichbar ist, faellt `src/services/mockAiCoach.ts` automatisch auf Beispielantworten zurueck. Die Modell-Konfiguration liegt in `src/config/ai.ts` und liest Werte aus `.env`.
The chat uses `src/services/ollamaClient.ts` for real local AI replies. If Ollama is not reachable, `src/services/mockAiCoach.ts` automatically falls back to example replies. Model configuration lives in `src/config/ai.ts` and reads values from `.env`.
Moeglicher lokaler Ablauf:
Local flow:
```text
React Native Chat UI
-> AI Service
-> lokale Ollama API
-> Modell wie llama, qwen oder mistral
-> Antwort mit Korrektur und Erklaerung
-> local Ollama API
-> model such as llama, qwen, or mistral
-> English-only correction and explanation
```
Fuer eine PWA oder lokale Web-Version kann die App spaeter im Browser laufen und mit einem lokalen Ollama-Server im Netzwerk verbunden werden.
For a PWA or local web version, the app can later run in the browser and connect to a local Ollama server on the network.
## PWA Idee

View File

@@ -18,7 +18,7 @@ export function ChatBubble({ message }: ChatBubbleProps) {
return (
<View style={[styles.wrapper, isUser ? styles.userWrapper : styles.aiWrapper]}>
<View style={[styles.bubble, isUser ? styles.userBubble : styles.aiBubble]}>
<Text style={styles.sender}>{isUser ? 'Du' : 'AI Coach'}</Text>
<Text style={styles.sender}>{isUser ? 'You' : 'AI Coach'}</Text>
<Text style={styles.text}>{message.text}</Text>
</View>
</View>

View File

@@ -6,13 +6,13 @@ export function ProgressCard() {
return (
<View style={styles.card}>
<View style={styles.row}>
<Text style={styles.label}>Heute gelernt</Text>
<Text style={styles.label}>Practice today</Text>
<Text style={styles.value}>12 min</Text>
</View>
<View style={styles.track}>
<View style={styles.fill} />
</View>
<Text style={styles.hint}>Daily Reminder: 18:30 Uhr, 10 Minuten Englisch ueben.</Text>
<Text style={styles.hint}>Daily Reminder: 6:30 PM, 10 minutes of English practice.</Text>
</View>
);
}

View File

@@ -3,6 +3,6 @@ export function useDailyReminder() {
return {
enabled: true,
time: '18:30',
label: 'Taegliche Uebung ist vorbereitet',
label: 'Daily practice is ready',
};
}

View File

@@ -21,7 +21,7 @@ export function AppNavigator() {
}}
>
<Stack.Screen name="Home" component={HomeScreen} options={{ title: 'English AI Coach' }} />
<Stack.Screen name="Level" component={LevelScreen} options={{ title: 'Level auswaehlen' }} />
<Stack.Screen name="Level" component={LevelScreen} options={{ title: 'Choose level' }} />
<Stack.Screen name="Chat" component={ChatScreen} options={{ title: 'AI Chat' }} />
</Stack.Navigator>
</NavigationContainer>

View File

@@ -19,7 +19,7 @@ export function ChatScreen({ route }: ChatScreenProps) {
{
id: 'welcome',
role: 'assistant',
text: `Willkommen! Dein Level ist ${level}. Schreib einen englischen Satz und ich korrigiere ihn.`,
text: `Welcome. Your level is ${level}. Write an English sentence and I will correct it.`,
},
]);
const [isSending, setIsSending] = useState(false);
@@ -34,7 +34,7 @@ export function ChatScreen({ route }: ChatScreenProps) {
setInput('');
setIsSending(true);
// This mock can later be replaced by Ollama or another AI API client.
// Uses Ollama when available and falls back to local examples when offline.
const reply = await getMockAiReply(userMessage.text, level);
setMessages((current) => [...current, { id: `ai-${Date.now()}`, role: 'assistant', text: reply }]);
speakText(reply);
@@ -59,21 +59,21 @@ export function ChatScreen({ route }: ChatScreenProps) {
<TextInput
multiline
onChangeText={setInput}
placeholder="Schreib einen englischen Satz..."
placeholder="Write an English sentence..."
placeholderTextColor={colors.textSecondary}
style={styles.input}
value={input}
/>
<View style={styles.actionRow}>
<View style={styles.actionItem}>
<PrimaryButton label={isSending ? 'Denke...' : 'Senden'} onPress={sendMessage} />
<PrimaryButton label={isSending ? 'Thinking...' : 'Send'} onPress={sendMessage} />
</View>
<View style={styles.actionItem}>
<PrimaryButton label="Stop" variant="secondary" onPress={stopSpeaking} />
</View>
</View>
<PrimaryButton
label="Letzte Antwort vorlesen"
label="Read last answer"
variant="secondary"
onPress={() => {
const lastAiMessage = [...messages].reverse().find((message) => message.role === 'assistant');

View File

@@ -18,9 +18,9 @@ export function HomeScreen({ navigation }: HomeScreenProps) {
<ScrollView contentContainerStyle={styles.content}>
<View style={styles.hero}>
<Text style={styles.eyebrow}>Personal English Tutor</Text>
<Text style={styles.title}>Lerne Englisch mit einem KI Coach</Text>
<Text style={styles.title}>Practice English with an AI teacher</Text>
<Text style={styles.subtitle}>
Starte mit deinem Level, schreibe kurze Saetze und erhalte Korrekturen mit deutscher Erklaerung.
Choose your level, write short sentences, and get clear corrections in English.
</Text>
</View>
@@ -29,10 +29,10 @@ export function HomeScreen({ navigation }: HomeScreenProps) {
<View style={styles.reminderCard}>
<Text style={styles.cardTitle}>Daily Reminder</Text>
<Text style={styles.cardText}>{reminder.label}</Text>
<Text style={styles.cardMeta}>{reminder.enabled ? reminder.time : 'Aus'}</Text>
<Text style={styles.cardMeta}>{reminder.enabled ? reminder.time : 'Off'}</Text>
</View>
<PrimaryButton label="Level waehlen" onPress={() => navigation.navigate('Level')} />
<PrimaryButton label="Choose level" onPress={() => navigation.navigate('Level')} />
</ScrollView>
</SafeAreaView>
);

View File

@@ -12,8 +12,8 @@ export function LevelScreen({ navigation }: LevelScreenProps) {
return (
<SafeAreaView style={styles.safeArea} edges={['bottom']}>
<ScrollView contentContainerStyle={styles.content}>
<Text style={styles.title}>Welches Englisch-Level passt zu dir?</Text>
<Text style={styles.subtitle}>Du kannst spaeter jederzeit wechseln. Das Level steuert die Mock-Antworten.</Text>
<Text style={styles.title}>Which English level fits you?</Text>
<Text style={styles.subtitle}>You can change it later. The level controls how simple the coach answers.</Text>
{levels.map((item) => (
<Pressable

View File

@@ -3,15 +3,15 @@ import { aiConfig } from '../config/ai';
import { askOllama } from './ollamaClient';
const examples: Record<EnglishLevel, string> = {
A1: 'Fast richtig! Correct: "I am learning English." Warum? Bei "I" nutzt du "am".',
A2: 'Good try. Correct: "I went to work yesterday." "Yesterday" braucht Past Tense.',
B1: 'Nice sentence. Alternative: "I have been practicing every day." Das klingt natuerlicher.',
A1: 'Good try! Correct: "I am learning English." Use "am" with "I" in the present continuous.',
A2: 'Good try. Correct: "I went to work yesterday." Use the past tense with "yesterday".',
B1: 'Nice sentence. Alternative: "I have been practicing every day." This sounds more natural.',
B2: 'Strong idea. Improve flow: use linking words like "however", "therefore" or "in addition".',
};
export async function getMockAiReply(input: string, level: EnglishLevel) {
if (!input.trim()) {
return 'Schreib einen kurzen englischen Satz, dann korrigiere ich ihn fuer dein Level.';
return 'Write a short English sentence and I will correct it for your level.';
}
try {
@@ -20,5 +20,5 @@ export async function getMockAiReply(input: string, level: EnglishLevel) {
await new Promise((resolve) => setTimeout(resolve, 350));
}
return `${examples[level]}\n\nDein Satz: "${input.trim()}"\n\nKI Backend vorbereitet: ${aiConfig.ollamaModel}`;
return `${examples[level]}\n\nYour sentence: "${input.trim()}"\n\nAI backend: ${aiConfig.ollamaModel}`;
}

View File

@@ -23,17 +23,19 @@ export async function askOllama(input: string, level: EnglishLevel) {
}
const data = (await response.json()) as OllamaGenerateResponse;
return data.response?.trim() || 'Ich konnte keine Antwort erzeugen.';
return data.response?.trim() || 'I could not generate a useful answer.';
}
function buildPrompt(input: string, level: EnglishLevel) {
return `Du bist ein freundlicher Englisch-Coach fuer deutsche Lernende.
return `You are a friendly English teacher and conversation coach.
Level: ${level}
Aufgabe:
1. Korrigiere den englischen Satz.
2. Erklaere den wichtigsten Fehler kurz auf Deutsch.
3. Gib eine bessere natuerliche Variante.
4. Antworte kurz und motivierend.
Rules:
1. Reply only in English.
2. Correct the user's sentence.
3. Explain the main mistake briefly in simple English.
4. Give one natural alternative sentence.
5. Keep the answer short, clear, and encouraging.
6. Do not use German.
Satz des Nutzers: ${input}`;
User sentence: ${input}`;
}

View File

@@ -1,8 +1,8 @@
import type { EnglishLevel } from '../navigation/types';
export const levels: Array<{ level: EnglishLevel; title: string; description: string }> = [
{ level: 'A1', title: 'Beginner', description: 'Einfache Saetze, Alltag und Grundlagen.' },
{ level: 'A2', title: 'Elementary', description: 'Vergangenheit, Fragen und kurze Dialoge.' },
{ level: 'B1', title: 'Intermediate', description: 'Freies Schreiben mit klaren Korrekturen.' },
{ level: 'B2', title: 'Upper Intermediate', description: 'Natuerlicher Ausdruck und bessere Struktur.' },
{ level: 'A1', title: 'Beginner', description: 'Simple sentences, daily life, and basics.' },
{ level: 'A2', title: 'Elementary', description: 'Past tense, questions, and short conversations.' },
{ level: 'B1', title: 'Intermediate', description: 'Free writing with clear corrections.' },
{ level: 'B2', title: 'Upper Intermediate', description: 'Natural expression and better structure.' },
];