feat(app): optionaler Quell-Badge an ARIA-Bubbles (Einstellung, pro Geraet, default aus)
Zeigt wie in Diagnostic, ob eine Antwort vom lokalen Modell / Claude / Fast-Path
kam — aber in der App als Opt-in, damit die App "Mama-tauglich" bleibt.
- ChatScreen: ChatMessage.answeredBy; aus dem chat-Payload UND der History-Sync
eingefangen; kleiner farbiger Badge im statusRow der ARIA-Bubble, nur wenn
showSource an. State aus AsyncStorage aria_show_source (default false, per
2s-Reload synchron mit den Settings).
- SettingsScreen: Toggle "Antwort-Quelle anzeigen" in der Chat-Bubbles-Card,
schreibt aria_show_source (pro Geraet — Stefan an, Mama aus).
- Bridge liefert answeredBy schon in chat_backup/History mit (kein Change noetig).
Server-Teil in 9cc1aec. APK-Rebuild noetig.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,9 @@ interface ChatMessage {
|
|||||||
/** Projekt-Zuordnung — leer = Hauptchat. Wird genutzt um Bubbles zu
|
/** Projekt-Zuordnung — leer = Hauptchat. Wird genutzt um Bubbles zu
|
||||||
* Projekt-Bloecken zu gruppieren (auf/einklappbar). */
|
* Projekt-Bloecken zu gruppieren (auf/einklappbar). */
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
|
/** Welcher Backend die Antwort erzeugt hat: 'local' | 'claude' | 'fast-path'.
|
||||||
|
* Fuer den optionalen Quell-Badge (Einstellung aria_show_source, default aus). */
|
||||||
|
answeredBy?: string;
|
||||||
/** Bridge-Message-ID zur Zuordnung von TTS-Audio */
|
/** Bridge-Message-ID zur Zuordnung von TTS-Audio */
|
||||||
messageId?: string;
|
messageId?: string;
|
||||||
/** Lokaler Pfad zur gecachten TTS-Audio-Datei (file://...) */
|
/** Lokaler Pfad zur gecachten TTS-Audio-Datei (file://...) */
|
||||||
@@ -324,6 +327,8 @@ const ChatScreen: React.FC = () => {
|
|||||||
// App soll sie standardmaessig NICHT anzeigen — Stefan sieht sonst
|
// App soll sie standardmaessig NICHT anzeigen — Stefan sieht sonst
|
||||||
// jeden Hint mit. Toggle in Settings.
|
// jeden Hint mit. Toggle in Settings.
|
||||||
const [showSystemHints, setShowSystemHints] = useState(false);
|
const [showSystemHints, setShowSystemHints] = useState(false);
|
||||||
|
// Quell-Badge (local/claude/fast-path) an ARIA-Bubbles — pro Geraet, default AUS.
|
||||||
|
const [showSource, setShowSource] = useState(false);
|
||||||
// Gerätelokale XTTS-Voice-Wahl (bevorzugt gegenueber dem globalen Default)
|
// Gerätelokale XTTS-Voice-Wahl (bevorzugt gegenueber dem globalen Default)
|
||||||
const localXttsVoiceRef = useRef<string>('');
|
const localXttsVoiceRef = useRef<string>('');
|
||||||
// Geraetelokale TTS-Wiedergabegeschwindigkeit (speed-Param an F5-TTS)
|
// Geraetelokale TTS-Wiedergabegeschwindigkeit (speed-Param an F5-TTS)
|
||||||
@@ -546,6 +551,8 @@ const ChatScreen: React.FC = () => {
|
|||||||
setGpsEnabled(gps === 'true');
|
setGpsEnabled(gps === 'true');
|
||||||
const hints = await AsyncStorage.getItem('aria_show_hints');
|
const hints = await AsyncStorage.getItem('aria_show_hints');
|
||||||
setShowSystemHints(hints === 'true'); // default false
|
setShowSystemHints(hints === 'true'); // default false
|
||||||
|
const src = await AsyncStorage.getItem('aria_show_source');
|
||||||
|
setShowSource(src === 'true'); // default false (Mama sieht keine Badges)
|
||||||
};
|
};
|
||||||
loadSettings();
|
loadSettings();
|
||||||
const interval = setInterval(loadSettings, 2000);
|
const interval = setInterval(loadSettings, 2000);
|
||||||
@@ -815,6 +822,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
attachments: attachments.length ? attachments : undefined,
|
attachments: attachments.length ? attachments : undefined,
|
||||||
backupTs: typeof m.ts === 'number' ? m.ts : undefined,
|
backupTs: typeof m.ts === 'number' ? m.ts : undefined,
|
||||||
projectId: typeof m.project_id === 'string' ? m.project_id : '',
|
projectId: typeof m.project_id === 'string' ? m.project_id : '',
|
||||||
|
answeredBy: typeof m.answeredBy === 'string' ? m.answeredBy : '',
|
||||||
...(cmid && { clientMsgId: cmid }),
|
...(cmid && { clientMsgId: cmid }),
|
||||||
// Server-Bubble = vom Brain verarbeitet → 'delivered' (✓✓)
|
// Server-Bubble = vom Brain verarbeitet → 'delivered' (✓✓)
|
||||||
...(role === 'user' && cmid && { deliveryStatus: 'delivered' as const }),
|
...(role === 'user' && cmid && { deliveryStatus: 'delivered' as const }),
|
||||||
@@ -1151,6 +1159,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
messageId: (message.payload.messageId as string) || undefined,
|
messageId: (message.payload.messageId as string) || undefined,
|
||||||
backupTs: (message.payload.backupTs as number) || undefined,
|
backupTs: (message.payload.backupTs as number) || undefined,
|
||||||
projectId: ((message.payload as any).projectId as string) || '',
|
projectId: ((message.payload as any).projectId as string) || '',
|
||||||
|
answeredBy: ((message.payload as any).answeredBy as string) || '',
|
||||||
};
|
};
|
||||||
// ARIA hat geantwortet → alle User-Bubbles davor als 'delivered'
|
// ARIA hat geantwortet → alle User-Bubbles davor als 'delivered'
|
||||||
// markieren (WhatsApp-Doppelhaken ✓✓). Brain hat sie verarbeitet.
|
// markieren (WhatsApp-Doppelhaken ✓✓). Brain hat sie verarbeitet.
|
||||||
@@ -2411,6 +2420,16 @@ const ChatScreen: React.FC = () => {
|
|||||||
) : null}
|
) : null}
|
||||||
<View style={styles.statusRow}>
|
<View style={styles.statusRow}>
|
||||||
<Text style={styles.timestamp}>{time}</Text>
|
<Text style={styles.timestamp}>{time}</Text>
|
||||||
|
{!isUser && showSource && item.answeredBy ? (() => {
|
||||||
|
const ab = item.answeredBy as string;
|
||||||
|
const map: { [k: string]: { t: string; c: string } } = {
|
||||||
|
local: { t: '⚡ lokal', c: '#34C759' },
|
||||||
|
claude: { t: 'Claude', c: '#0096FF' },
|
||||||
|
'fast-path': { t: '⚡ fast', c: '#AF7BFF' },
|
||||||
|
};
|
||||||
|
const b = map[ab] || { t: ab, c: '#8A8AA0' };
|
||||||
|
return <Text style={{ fontSize: 9, fontWeight: 'bold', color: b.c, marginLeft: 6 }}>{b.t}</Text>;
|
||||||
|
})() : null}
|
||||||
{item.text.length > 0 ? (
|
{item.text.length > 0 ? (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
hitSlop={{top:6,bottom:6,left:6,right:6}}
|
hitSlop={{top:6,bottom:6,left:6,right:6}}
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ const SettingsScreen: React.FC = () => {
|
|||||||
const [bgGpsEnabled, setBgGpsEnabled] = useState(false);
|
const [bgGpsEnabled, setBgGpsEnabled] = useState(false);
|
||||||
const [backgroundMode, setBackgroundMode] = useState(true); // Default an
|
const [backgroundMode, setBackgroundMode] = useState(true); // Default an
|
||||||
const [showSystemHints, setShowSystemHints] = useState(false); // Default aus
|
const [showSystemHints, setShowSystemHints] = useState(false); // Default aus
|
||||||
|
const [showSource, setShowSource] = useState(false); // Quell-Badge, Default aus
|
||||||
const [scannerVisible, setScannerVisible] = useState(false);
|
const [scannerVisible, setScannerVisible] = useState(false);
|
||||||
const [logTab, setLogTab] = useState<LogTab>('live');
|
const [logTab, setLogTab] = useState<LogTab>('live');
|
||||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||||
@@ -261,6 +262,9 @@ const SettingsScreen: React.FC = () => {
|
|||||||
// Default ist aus — nur explicit 'true' aktiviert
|
// Default ist aus — nur explicit 'true' aktiviert
|
||||||
setShowSystemHints(saved === 'true');
|
setShowSystemHints(saved === 'true');
|
||||||
});
|
});
|
||||||
|
AsyncStorage.getItem('aria_show_source').then(saved => {
|
||||||
|
setShowSource(saved === 'true'); // Default aus
|
||||||
|
});
|
||||||
// gpsTrackingService status syncen + auf Aenderungen lauschen
|
// gpsTrackingService status syncen + auf Aenderungen lauschen
|
||||||
setGpsTracking(gpsTrackingService.isActive());
|
setGpsTracking(gpsTrackingService.isActive());
|
||||||
const offGps = gpsTrackingService.onChange(setGpsTracking);
|
const offGps = gpsTrackingService.onChange(setGpsTracking);
|
||||||
@@ -857,6 +861,11 @@ const SettingsScreen: React.FC = () => {
|
|||||||
AsyncStorage.setItem('aria_show_hints', String(value)).catch(() => {});
|
AsyncStorage.setItem('aria_show_hints', String(value)).catch(() => {});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleShowSourceToggle = useCallback((value: boolean) => {
|
||||||
|
setShowSource(value);
|
||||||
|
AsyncStorage.setItem('aria_show_source', String(value)).catch(() => {});
|
||||||
|
}, []);
|
||||||
|
|
||||||
// --- XTTS Voice ---
|
// --- XTTS Voice ---
|
||||||
|
|
||||||
const selectVoice = useCallback((voiceName: string) => {
|
const selectVoice = useCallback((voiceName: string) => {
|
||||||
@@ -1580,6 +1589,22 @@ const SettingsScreen: React.FC = () => {
|
|||||||
thumbColor={showSystemHints ? '#FFFFFF' : '#666680'}
|
thumbColor={showSystemHints ? '#FFFFFF' : '#666680'}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
<View style={styles.toggleRow}>
|
||||||
|
<View style={styles.toggleInfo}>
|
||||||
|
<Text style={styles.toggleLabel}>Antwort-Quelle anzeigen</Text>
|
||||||
|
<Text style={styles.toggleHint}>
|
||||||
|
Kleiner Badge an ARIAs Bubbles: ob die Antwort vom schnellen
|
||||||
|
lokalen Modell, von Claude oder per Direkt-Befehl kam. Nur fuer
|
||||||
|
dich interessant (Technik) — standardmaessig aus.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Switch
|
||||||
|
value={showSource}
|
||||||
|
onValueChange={handleShowSourceToggle}
|
||||||
|
trackColor={{ false: '#2A2A3E', true: '#0096FF' }}
|
||||||
|
thumbColor={showSource ? '#FFFFFF' : '#666680'}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* === Hintergrund-Modus === */}
|
{/* === Hintergrund-Modus === */}
|
||||||
|
|||||||
Reference in New Issue
Block a user