fix: Arabic STT + DM scroll + info sheet FormSheet
STT (Arabic):
- Deepgram hat nova-2-general ar/tr-Support eingestellt (400 Bad Request)
- Fix: einheitlich nova-3 für alle Sprachen inkl. ar/tr
- Stale Kommentar aus 2026-05-30 entfernt
DM scroll-to-bottom:
- onLayout auf FlatList hinzugefügt → zusätzlicher scrollToEnd nach
initialem Layout-Pass (Android-specific race condition)
- onOpenImage im FlatList-Renderer auf Lightbox verdrahtet (war () => {})
Info-Sheet:
- Modal(pageSheet) → FormSheet mit initialHeightPct={0.85}
- Nutzt jetzt unsere eigene Sheet-Komponente konsistent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0533fcad71
commit
89391a807b
@ -28,6 +28,7 @@ import * as FileSystem from 'expo-file-system/legacy';
|
||||
import { apiFetch } from '../lib/api';
|
||||
import { ChatBubble, type ChatMsg, type MessageReaction } from '../components/chat/ChatBubble';
|
||||
import { DmChatBackground } from '../components/chat/DmChatBackground';
|
||||
import { FormSheet } from '../components/FormSheet';
|
||||
import { useDmRealtime } from '../hooks/useChatRealtime';
|
||||
import { useColors } from '../lib/theme';
|
||||
import { useThemeStore } from '../stores/theme';
|
||||
@ -532,7 +533,7 @@ export default function DmScreen() {
|
||||
onLike={toggleLike}
|
||||
onReact={toggleReaction}
|
||||
onDelete={deleteMessage}
|
||||
onOpenImage={() => {}}
|
||||
onOpenImage={(url) => setLightboxUri(url)}
|
||||
/>
|
||||
)}
|
||||
keyExtractor={(m) => m.id}
|
||||
@ -545,6 +546,7 @@ export default function DmScreen() {
|
||||
keyboardDismissMode="interactive"
|
||||
keyboardShouldPersistTaps="handled"
|
||||
onContentSizeChange={() => flatListRef.current?.scrollToEnd({ animated: false })}
|
||||
onLayout={() => flatListRef.current?.scrollToEnd({ animated: false })}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
@ -706,103 +708,90 @@ function DmInfoSheet({
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal visible={visible} animationType="slide" presentationStyle="pageSheet" onRequestClose={onClose}>
|
||||
<View style={{ flex: 1, backgroundColor: colors.bg }}>
|
||||
{/* Header */}
|
||||
<View style={{
|
||||
flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16,
|
||||
paddingTop: 16, paddingBottom: 12,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: colors.border,
|
||||
}}>
|
||||
<Text style={{ flex: 1, fontSize: 17, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{t('chat.info')}
|
||||
</Text>
|
||||
<TouchableOpacity onPress={onClose} hitSlop={8} activeOpacity={0.7}>
|
||||
<Ionicons name="close" size={24} color={colors.text} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
<ScrollView contentContainerStyle={{ paddingBottom: 40 }}>
|
||||
{/* Partner-Karte */}
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={onViewProfile}
|
||||
style={{
|
||||
flexDirection: 'row', alignItems: 'center',
|
||||
padding: 16, gap: 14,
|
||||
}}
|
||||
>
|
||||
{partner?.avatar ? (
|
||||
<Image
|
||||
source={{ uri: partner.avatar }}
|
||||
style={{ width: 56, height: 56, borderRadius: 28 }}
|
||||
contentFit="cover"
|
||||
cachePolicy="memory-disk"
|
||||
/>
|
||||
) : (
|
||||
<View style={{
|
||||
width: 56, height: 56, borderRadius: 28,
|
||||
backgroundColor: colors.brandOrange + '30',
|
||||
alignItems: 'center', justifyContent: 'center',
|
||||
}}>
|
||||
<Text style={{ fontSize: 20, fontFamily: 'Nunito_700Bold', color: colors.brandOrange }}>
|
||||
{partner?.nickname?.[0]?.toUpperCase() ?? '?'}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ fontSize: 17, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{partner?.nickname ?? '…'}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, fontFamily: 'Nunito_400Regular', color: colors.textMuted, marginTop: 2 }}>
|
||||
{t('dm.view_profile', { defaultValue: 'Profil anzeigen' })}
|
||||
</Text>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={18} color={colors.textMuted} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={{ height: StyleSheet.hairlineWidth, backgroundColor: colors.border, marginHorizontal: 16 }} />
|
||||
|
||||
{/* Geteilte Medien */}
|
||||
<View style={{ paddingHorizontal: 16, paddingTop: 20, paddingBottom: 12 }}>
|
||||
<Text style={{ fontSize: 15, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{t('dm.shared_media', { defaultValue: 'Geteilte Medien' })}
|
||||
{sharedMedia.length > 0 && (
|
||||
<Text style={{ fontSize: 13, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>
|
||||
{' '}{sharedMedia.length}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{sharedMedia.length === 0 ? (
|
||||
<View style={{ alignItems: 'center', paddingVertical: 32 }}>
|
||||
<Ionicons name="images-outline" size={40} color={colors.textMuted} />
|
||||
<Text style={{ marginTop: 10, fontSize: 13, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>
|
||||
{t('dm.no_shared_media', { defaultValue: 'Keine geteilten Medien' })}
|
||||
</Text>
|
||||
</View>
|
||||
<FormSheet
|
||||
visible={visible}
|
||||
onClose={onClose}
|
||||
title={t('chat.info')}
|
||||
initialHeightPct={0.85}
|
||||
dismissOnBackdrop
|
||||
>
|
||||
<ScrollView contentContainerStyle={{ paddingBottom: 40 }}>
|
||||
{/* Partner-Karte */}
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={onViewProfile}
|
||||
style={{ flexDirection: 'row', alignItems: 'center', padding: 16, gap: 14 }}
|
||||
>
|
||||
{partner?.avatar ? (
|
||||
<Image
|
||||
source={{ uri: partner.avatar }}
|
||||
style={{ width: 56, height: 56, borderRadius: 28 }}
|
||||
contentFit="cover"
|
||||
cachePolicy="memory-disk"
|
||||
/>
|
||||
) : (
|
||||
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: MEDIA_GAP, paddingHorizontal: MEDIA_GAP }}>
|
||||
{[...sharedMedia].reverse().map((m) => (
|
||||
<TouchableOpacity
|
||||
key={m.id}
|
||||
activeOpacity={0.8}
|
||||
onPress={() => onImagePress(m.attachmentUrl!)}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: m.attachmentUrl! }}
|
||||
style={{ width: MEDIA_SIZE, height: MEDIA_SIZE }}
|
||||
contentFit="cover"
|
||||
cachePolicy="memory-disk"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
<View style={{
|
||||
width: 56, height: 56, borderRadius: 28,
|
||||
backgroundColor: colors.brandOrange + '30',
|
||||
alignItems: 'center', justifyContent: 'center',
|
||||
}}>
|
||||
<Text style={{ fontSize: 20, fontFamily: 'Nunito_700Bold', color: colors.brandOrange }}>
|
||||
{partner?.nickname?.[0]?.toUpperCase() ?? '?'}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</Modal>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ fontSize: 17, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{partner?.nickname ?? '…'}
|
||||
</Text>
|
||||
<Text style={{ fontSize: 13, fontFamily: 'Nunito_400Regular', color: colors.textMuted, marginTop: 2 }}>
|
||||
{t('dm.view_profile')}
|
||||
</Text>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={18} color={colors.textMuted} />
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={{ height: StyleSheet.hairlineWidth, backgroundColor: colors.border, marginHorizontal: 16 }} />
|
||||
|
||||
{/* Geteilte Medien */}
|
||||
<View style={{ paddingHorizontal: 16, paddingTop: 20, paddingBottom: 12, flexDirection: 'row', alignItems: 'baseline', gap: 6 }}>
|
||||
<Text style={{ fontSize: 15, fontFamily: 'Nunito_700Bold', color: colors.text }}>
|
||||
{t('dm.shared_media')}
|
||||
</Text>
|
||||
{sharedMedia.length > 0 && (
|
||||
<Text style={{ fontSize: 13, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>
|
||||
{sharedMedia.length}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{sharedMedia.length === 0 ? (
|
||||
<View style={{ alignItems: 'center', paddingVertical: 32 }}>
|
||||
<Ionicons name="images-outline" size={40} color={colors.textMuted} />
|
||||
<Text style={{ marginTop: 10, fontSize: 13, fontFamily: 'Nunito_400Regular', color: colors.textMuted }}>
|
||||
{t('dm.no_shared_media')}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: MEDIA_GAP, paddingHorizontal: MEDIA_GAP }}>
|
||||
{[...sharedMedia].reverse().map((m) => (
|
||||
<TouchableOpacity
|
||||
key={m.id}
|
||||
activeOpacity={0.8}
|
||||
onPress={() => onImagePress(m.attachmentUrl!)}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: m.attachmentUrl! }}
|
||||
style={{ width: MEDIA_SIZE, height: MEDIA_SIZE }}
|
||||
contentFit="cover"
|
||||
cachePolicy="memory-disk"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
</FormSheet>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -84,34 +84,18 @@ export default defineEventHandler(async (event) => {
|
||||
);
|
||||
|
||||
// Deepgram language mapping.
|
||||
// Live-Diagnose (2026-05-30): nova-3 lehnt language=ar (und tr) mit
|
||||
// 400 "No such model/language/tier combination found" ab — entgegen
|
||||
// der vorherigen Annahme. Fallback für ar/tr: nova-2-general
|
||||
// (multilingual auto-detect). Für alle anderen Sprachen bleibt nova-3
|
||||
// (bessere Genauigkeit, diskrete language-codes).
|
||||
// Stand 2026-06-01: nova-3 unterstützt alle Sprachen inkl. ar/tr.
|
||||
// nova-2-general hat ar/tr-Support eingestellt ("No such model/language/tier
|
||||
// combination found") — daher einheitlich nova-3 für alle Sprachen.
|
||||
const deepgramLang =
|
||||
language &&
|
||||
["de", "en", "tr", "ar", "fr", "es", "pt", "it"].includes(language)
|
||||
? language
|
||||
: "de";
|
||||
|
||||
// nova-2-general unterstützt language=ar/tr (im Gegensatz zu nova-3).
|
||||
// Ohne expliziten language-Param fällt nova-2 auf Auto-Detect zurück und
|
||||
// misdetektiert arabisches Audio oft als Englisch (phonetisches Transcript
|
||||
// wie "salam alaikum" statt "السلام عليكم") — Lyra antwortet dann nicht
|
||||
// auf Arabisch. Mit language=ar wird der korrekte Acoustic-Model-Pfad
|
||||
// verwendet und die Schrift bleibt arabisch.
|
||||
const needsGeneralModel = ["ar", "tr"].includes(deepgramLang);
|
||||
const deepgramUrl = needsGeneralModel
|
||||
? `https://api.deepgram.com/v1/listen?language=${deepgramLang}&model=nova-2-general`
|
||||
: `https://api.deepgram.com/v1/listen?language=${deepgramLang}&model=nova-3`;
|
||||
const deepgramUrl = `https://api.deepgram.com/v1/listen?language=${deepgramLang}&model=nova-3`;
|
||||
|
||||
console.log(
|
||||
"[transcribe] language:",
|
||||
deepgramLang,
|
||||
"model:",
|
||||
needsGeneralModel ? "nova-2-general" : "nova-3",
|
||||
);
|
||||
console.log("[transcribe] language:", deepgramLang, "model: nova-3");
|
||||
|
||||
try {
|
||||
const response = await fetch(deepgramUrl, {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user