feat(app): Datei-Manager, Skill-Created-Bubble, Zoom rewriten, Repair-Cleanup

Drei groessere Aenderungen in der Android-App.

Datei-Manager (Settings → Dateien)
  - Neuer Eintrag im Settings-Menue → Modal mit Liste
  - Suche + Filter (Alle / Von ARIA / Vom User)
  - Per Eintrag: ARIA/USER-Badge, Groesse, Datum, Loeschen-Button
  - file_list_request via RVS → Bridge → Diagnostic-HTTP → response
  - file_delete_request loescht serverseitig, file_deleted-Event
    aktualisiert ALLE Chat-Bubbles (Attachment.deleted = true mit
    Strikethrough-Name + 🗑️-Icon)

Skill-Created-Bubble
  - Neuer ChatMessage.skillCreated Typ — eigenes Render mit gelbem
    Border, Skill-Name, Beschreibung, Execution-Mode, Active-Status
  - Falls Skill-Setup fehlschlug: ⚠ Setup-Fehler-Zeile direkt in der Bubble
  - Stefan sieht in der Chat-History immer wenn ARIA selbst einen
    Skill angelegt hat — Transparenz statt schweigend im Hintergrund

Pinch-Zoom rewriten (ZoomableImage.tsx)
  - Multi-Touch-Race-Bugs in der alten Variante geloest:
    * Touch-Count jetzt aus e.nativeEvent.touches.length statt
      gestureState.numberActiveTouches (war nicht zuverlaessig)
    * Re-Snapshot bei JEDEM Finger-Wechsel (1↔2) → keine Spruenge mehr
    * Doppel-Tap via onPanResponderRelease + Bewegungs-Cap
    * pointerEvents="none" auf Image-Wrapper → Touches gehen garantiert
      an PanResponder-View
    * collapsable={false} verhindert Android-View-Flattening
  - 2-Finger-Pinch 1x..5x, simultaner Pan via Focal,
    1-Finger-Pan nur wenn gezoomt (>1.02x), Doppel-Tap toggelt 1x↔2.5x

App SettingsScreen Repair-Section
  - aria-core-spezifische Buttons raus: 🔧 Reparieren, 🚨 ARIA hart neu,
    🧹 Konversation komprimieren (OpenClaw ist abgerissen)
  - Stattdessen generischer container_restart fuer aria-bridge/brain/qdrant
  - Repair-Buttons aus der "ARIA denkt..."-Bubble entfernt (nur Abbrechen)

ChatScreen
  - skill_created und file_deleted Handler im RVS-Message-Switch
  - file_list_response (Modal-State liegt in SettingsScreen)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-11 22:24:06 +02:00
parent 0f9a029269
commit dc2f4eb6d2
3 changed files with 457 additions and 190 deletions
+74 -19
View File
@@ -54,6 +54,7 @@ interface Attachment {
uri?: string; // Lokaler Pfad (file://) fuer Anzeige
mimeType?: string;
serverPath?: string; // Pfad auf dem Server (/shared/uploads/...) fuer Re-Download
deleted?: boolean; // Datei wurde nachtraeglich geloescht (Diagnostic-Manager)
}
interface ChatMessage {
@@ -70,6 +71,14 @@ interface ChatMessage {
* gespiegelt damit wir die EXAKT richtige Placeholder-Bubble ersetzen,
* auch wenn mehrere Aufnahmen parallel offen sind. */
audioRequestId?: string;
/** Skill-Created-Bubble: ARIA hat einen neuen Skill angelegt */
skillCreated?: {
name: string;
description: string;
execution: string;
active: boolean;
setupError?: string;
};
}
// --- Konstanten ---
@@ -386,6 +395,41 @@ const ChatScreen: React.FC = () => {
return;
}
// skill_created: ARIA hat einen neuen Skill angelegt → eigene Bubble
if (message.type === 'skill_created') {
const p = (message.payload || {}) as any;
const skillMsg: ChatMessage = {
id: nextId(),
sender: 'aria',
text: '',
timestamp: Date.now(),
skillCreated: {
name: String(p.name || '(unbenannt)'),
description: String(p.description || ''),
execution: String(p.execution || 'bash'),
active: p.active !== false,
setupError: p.setup_error ? String(p.setup_error) : undefined,
},
};
setMessages(prev => capMessages([...prev, skillMsg]));
return;
}
// file_deleted: Datei wurde geloescht (vom Diagnostic User) → Bubble updaten
if (message.type === 'file_deleted') {
const p = (message.payload?.path as string) || '';
if (!p) return;
setMessages(prev => prev.map(m => ({
...m,
attachments: m.attachments?.map(a =>
a.serverPath === p ? { ...a, deleted: true } : a
),
})));
return;
}
// file_list_response: wird vom Datei-Manager im SettingsScreen verarbeitet.
// file_from_aria: ARIA hat eine Datei rausgegeben → als ARIA-Bubble anzeigen
if (message.type === 'file_from_aria') {
const p = message.payload || {};
@@ -1038,12 +1082,41 @@ const ChatScreen: React.FC = () => {
minute: '2-digit',
});
// Spezial-Bubble: ARIA hat einen Skill erstellt
if (item.skillCreated) {
const s = item.skillCreated;
return (
<View style={[styles.messageBubble, styles.ariaBubble, {borderLeftWidth: 3, borderLeftColor: '#FFD60A'}]}>
<Text style={{color: '#FFD60A', fontWeight: 'bold', fontSize: 14}}>
{'🛠 ARIA hat einen neuen Skill erstellt'}
</Text>
<Text style={{color: '#E0E0F0', marginTop: 4, fontSize: 14}}>
<Text style={{fontWeight: 'bold'}}>{s.name}</Text>
<Text style={{color: '#8888AA', fontSize: 12}}>{` (${s.execution}, ${s.active ? 'aktiv' : 'deaktiviert'})`}</Text>
</Text>
<Text style={{color: '#8888AA', fontSize: 12, marginTop: 2}}>{s.description}</Text>
{s.setupError && (
<Text style={{color: '#FF6B6B', fontSize: 11, marginTop: 4}}>
{'⚠ Setup-Fehler: '}{s.setupError.slice(0, 200)}
</Text>
)}
<Text style={{color: '#555570', fontSize: 10, marginTop: 6}}>ARIA-Skill · {time}</Text>
</View>
);
}
return (
<View style={[styles.messageBubble, isUser ? styles.userBubble : styles.ariaBubble]}>
{/* Anhang-Vorschau */}
{item.attachments?.map((att, idx) => (
<View key={idx}>
{att.type === 'image' && att.uri ? (
{att.deleted ? (
<View style={[styles.attachmentFile, {opacity: 0.6}]}>
<Text style={styles.attachmentFileIcon}>{'🗑️'}</Text>
<Text style={[styles.attachmentFileName, {textDecorationLine: 'line-through'}]} numberOfLines={1}>{att.name}</Text>
<Text style={[styles.attachmentFileSize, {color: '#FF9500'}]}>(geloescht)</Text>
</View>
) : att.type === 'image' && att.uri ? (
<ChatImage
uri={att.uri}
onPress={() => setFullscreenImage(att.uri || null)}
@@ -1253,24 +1326,6 @@ const ChatScreen: React.FC = () => {
: '\uD83D\uDCAD ARIA denkt...'}
</Text>
<View style={{flexDirection: 'row', gap: 6}}>
<TouchableOpacity style={[styles.thinkingCancel, {borderColor: '#FF9500'}]} onPress={() => rvs.send('doctor_fix' as any, {})}>
<Text style={[styles.thinkingCancelText, {color: '#FF9500'}]}>{'🔧'}</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.thinkingCancel, {borderColor: '#FF3B30'}]}
onPress={() => {
Alert.alert(
'ARIA hart neu starten?',
'Container-Restart (~15s). Laufende Anfragen gehen verloren.',
[
{ text: 'Abbrechen', style: 'cancel' },
{ text: 'Neu starten', style: 'destructive', onPress: () => rvs.send('aria_restart' as any, {}) },
],
);
}}
>
<Text style={[styles.thinkingCancelText, {color: '#FF3B30'}]}>{'🚨'}</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.thinkingCancel} onPress={cancelRequest}>
<Text style={styles.thinkingCancelText}>Abbrechen</Text>
</TouchableOpacity>