Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d8b05082d6 | |||
| de91073b2e | |||
| e88b5f57bf | |||
| 64a17c8c19 | |||
| ebeacba8b5 |
@@ -79,8 +79,8 @@ android {
|
|||||||
applicationId "com.ariacockpit"
|
applicationId "com.ariacockpit"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 10301
|
versionCode 10304
|
||||||
versionName "0.1.3.1"
|
versionName "0.1.3.4"
|
||||||
// Fallback fuer Libraries mit Product Flavors
|
// Fallback fuer Libraries mit Product Flavors
|
||||||
missingDimensionStrategy 'react-native-camera', 'general'
|
missingDimensionStrategy 'react-native-camera', 'general'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aria-cockpit",
|
"name": "aria-cockpit",
|
||||||
"version": "0.1.3.1",
|
"version": "0.1.3.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"android": "react-native run-android",
|
"android": "react-native run-android",
|
||||||
|
|||||||
@@ -37,9 +37,11 @@ interface Props {
|
|||||||
title?: string;
|
title?: string;
|
||||||
/** Style-Erweiterung fuer den Container. */
|
/** Style-Erweiterung fuer den Container. */
|
||||||
flatStyle?: boolean;
|
flatStyle?: boolean;
|
||||||
|
/** Wenn gesetzt: kein eigenes DetailModal mounten — Parent kuemmert sich. */
|
||||||
|
onOpenMemory?: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MemoryBrowser: React.FC<Props> = ({ restrictToIds, title, flatStyle }) => {
|
export const MemoryBrowser: React.FC<Props> = ({ restrictToIds, title, flatStyle, onOpenMemory }) => {
|
||||||
const [items, setItems] = useState<Memory[]>([]);
|
const [items, setItems] = useState<Memory[]>([]);
|
||||||
const [filtered, setFiltered] = useState<Memory[]>([]);
|
const [filtered, setFiltered] = useState<Memory[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -82,38 +84,35 @@ export const MemoryBrowser: React.FC<Props> = ({ restrictToIds, title, flatStyle
|
|||||||
setFiltered(out);
|
setFiltered(out);
|
||||||
}, [items, q, typeFilter, pinnedFilter, restrictToIds]);
|
}, [items, q, typeFilter, pinnedFilter, restrictToIds]);
|
||||||
|
|
||||||
|
const [showNewMemoryDialog, setShowNewMemoryDialog] = useState(false);
|
||||||
|
const [newMemoryTitle, setNewMemoryTitle] = useState('');
|
||||||
|
|
||||||
const onAddNew = () => {
|
const onAddNew = () => {
|
||||||
Alert.prompt(
|
setNewMemoryTitle('');
|
||||||
'Neue Memory',
|
setShowNewMemoryDialog(true);
|
||||||
'Titel:',
|
};
|
||||||
[
|
|
||||||
{ text: 'Abbrechen', style: 'cancel' },
|
const confirmAddNew = async () => {
|
||||||
{
|
const t = newMemoryTitle.trim();
|
||||||
text: 'Anlegen',
|
if (!t) { setShowNewMemoryDialog(false); return; }
|
||||||
onPress: async (title?: string) => {
|
setShowNewMemoryDialog(false);
|
||||||
const t = (title || '').trim();
|
try {
|
||||||
if (!t) return;
|
const m = await brainApi.saveMemory({
|
||||||
try {
|
type: 'fact', title: t,
|
||||||
const m = await brainApi.saveMemory({
|
content: '(noch leer — bitte editieren)',
|
||||||
type: 'fact', title: t,
|
});
|
||||||
content: '(noch leer — bitte editieren)',
|
load();
|
||||||
});
|
if (onOpenMemory) onOpenMemory(m.id);
|
||||||
load();
|
else setOpenId(m.id);
|
||||||
setOpenId(m.id);
|
} catch (e: any) {
|
||||||
} catch (e: any) {
|
Alert.alert('Fehler', String(e?.message || e));
|
||||||
Alert.alert('Fehler', String(e?.message || e));
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'plain-text',
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderItem = ({ item }: { item: Memory }) => {
|
const renderItem = ({ item }: { item: Memory }) => {
|
||||||
const attCount = (item.attachments || []).length;
|
const attCount = (item.attachments || []).length;
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity style={s.row} onPress={() => setOpenId(item.id)}>
|
<TouchableOpacity style={s.row} onPress={() => onOpenMemory ? onOpenMemory(item.id) : setOpenId(item.id)}>
|
||||||
<View style={{flex:1}}>
|
<View style={{flex:1}}>
|
||||||
<Text style={s.rowTitle} numberOfLines={1}>
|
<Text style={s.rowTitle} numberOfLines={1}>
|
||||||
{item.pinned ? '📌 ' : ''}{item.title || '(ohne Titel)'}
|
{item.pinned ? '📌 ' : ''}{item.title || '(ohne Titel)'}
|
||||||
@@ -202,12 +201,42 @@ export const MemoryBrowser: React.FC<Props> = ({ restrictToIds, title, flatStyle
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<MemoryDetailModal
|
{/* Eigenes DetailModal nur wenn der Parent kein Callback uebergibt
|
||||||
memoryId={openId}
|
(vermeidet Modal-in-Modal-Stacking auf Android). */}
|
||||||
visible={!!openId}
|
{!onOpenMemory && (
|
||||||
onClose={() => { setOpenId(null); load(); }}
|
<MemoryDetailModal
|
||||||
onDeleted={() => { setOpenId(null); load(); }}
|
memoryId={openId}
|
||||||
/>
|
visible={!!openId}
|
||||||
|
onClose={() => { setOpenId(null); load(); }}
|
||||||
|
onDeleted={() => { setOpenId(null); load(); }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* "Neue Memory"-Dialog (Alert.prompt ist iOS-only, daher eigenes Modal) */}
|
||||||
|
<Modal visible={showNewMemoryDialog} transparent animationType="fade" onRequestClose={() => setShowNewMemoryDialog(false)}>
|
||||||
|
<View style={s.menuBack}>
|
||||||
|
<View style={[s.menuBox, {padding:16, minWidth:280}]}>
|
||||||
|
<Text style={{color:'#FFD60A', fontWeight:'bold', fontSize:14, marginBottom:10}}>Neue Memory anlegen</Text>
|
||||||
|
<Text style={{color:'#8888AA', fontSize:11, marginBottom:6}}>Titel:</Text>
|
||||||
|
<TextInput
|
||||||
|
value={newMemoryTitle}
|
||||||
|
onChangeText={setNewMemoryTitle}
|
||||||
|
autoFocus
|
||||||
|
placeholder="z.B. Stefans Auto"
|
||||||
|
placeholderTextColor="#555570"
|
||||||
|
style={{backgroundColor:'#1E1E2E', color:'#E0E0F0', padding:8, borderRadius:4, fontSize:13, marginBottom:12}}
|
||||||
|
/>
|
||||||
|
<View style={{flexDirection:'row', gap:8, justifyContent:'flex-end'}}>
|
||||||
|
<TouchableOpacity onPress={() => setShowNewMemoryDialog(false)} style={{padding:8}}>
|
||||||
|
<Text style={{color:'#8888AA'}}>Abbrechen</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity onPress={confirmAddNew} style={{backgroundColor:'#0096FF', paddingHorizontal:14, paddingVertical:8, borderRadius:4}}>
|
||||||
|
<Text style={{color:'#fff', fontWeight:'600'}}>Anlegen</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1026,7 +1026,15 @@ const ChatScreen: React.FC = () => {
|
|||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
||||||
// Inverted FlatList: neueste Nachrichten unten, kein manuelles Scrollen noetig
|
// Inverted FlatList: neueste Nachrichten unten, kein manuelles Scrollen noetig
|
||||||
const invertedMessages = useMemo(() => [...messages].reverse(), [messages]);
|
// Spezial-Bubbles (memorySaved/triggerCreated/skillCreated) sollen im Chat
|
||||||
|
// NICHT mehr erscheinen — sie werden in der Notizen-Inbox angezeigt.
|
||||||
|
// Das verhindert dass sie chronologisch unten im Chat haengen und der
|
||||||
|
// eigentliche Chat-Verlauf darunter verschwindet.
|
||||||
|
const chatVisibleMessages = useMemo(
|
||||||
|
() => messages.filter(m => !m.memorySaved && !m.triggerCreated && !m.skillCreated),
|
||||||
|
[messages],
|
||||||
|
);
|
||||||
|
const invertedMessages = useMemo(() => [...chatVisibleMessages].reverse(), [chatVisibleMessages]);
|
||||||
|
|
||||||
// Such-Treffer: alle Message-IDs die zur Query passen, in chronologischer
|
// Such-Treffer: alle Message-IDs die zur Query passen, in chronologischer
|
||||||
// Reihenfolge (aelteste zuerst). Bei Query-Change resetten wir den Index.
|
// Reihenfolge (aelteste zuerst). Bei Query-Change resetten wir den Index.
|
||||||
@@ -1597,7 +1605,7 @@ const ChatScreen: React.FC = () => {
|
|||||||
connectionState === 'connecting' ? 'Verbinde...' : 'Getrennt'}
|
connectionState === 'connecting' ? 'Verbinde...' : 'Getrennt'}
|
||||||
</Text>
|
</Text>
|
||||||
<TouchableOpacity onPress={() => setInboxVisible(true)} style={{marginLeft: 'auto', paddingHorizontal: 6}} hitSlop={{top:8,bottom:8,left:6,right:6}}>
|
<TouchableOpacity onPress={() => setInboxVisible(true)} style={{marginLeft: 'auto', paddingHorizontal: 6}} hitSlop={{top:8,bottom:8,left:6,right:6}}>
|
||||||
<Text style={{fontSize: 18}}>\uD83D\uDDC2\uFE0F</Text>
|
<Text style={{fontSize: 18}}>{'\uD83D\uDDC2\uFE0F'}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<TouchableOpacity onPress={() => setSearchVisible(!searchVisible)} style={{paddingHorizontal: 6}} hitSlop={{top:8,bottom:8,left:6,right:6}}>
|
<TouchableOpacity onPress={() => setSearchVisible(!searchVisible)} style={{paddingHorizontal: 6}} hitSlop={{top:8,bottom:8,left:6,right:6}}>
|
||||||
<Text style={{fontSize: 16}}>{'\uD83D\uDD0D'}</Text>
|
<Text style={{fontSize: 16}}>{'\uD83D\uDD0D'}</Text>
|
||||||
@@ -1844,12 +1852,88 @@ const ChatScreen: React.FC = () => {
|
|||||||
<Modal visible={inboxVisible} animationType="slide" onRequestClose={() => setInboxVisible(false)}>
|
<Modal visible={inboxVisible} animationType="slide" onRequestClose={() => setInboxVisible(false)}>
|
||||||
<View style={{flex:1, backgroundColor:'#0D0D1A'}}>
|
<View style={{flex:1, backgroundColor:'#0D0D1A'}}>
|
||||||
<View style={{flexDirection:'row', alignItems:'center', padding:14, borderBottomWidth:1, borderBottomColor:'#1E1E2E'}}>
|
<View style={{flexDirection:'row', alignItems:'center', padding:14, borderBottomWidth:1, borderBottomColor:'#1E1E2E'}}>
|
||||||
<Text style={{color:'#FFD60A', fontWeight:'bold', fontSize:16, flex:1}}>🗂️ Notizen-Inbox</Text>
|
<Text style={{color:'#FFD60A', fontWeight:'bold', fontSize:16, flex:1}}>{'🗂️'} Notizen-Inbox</Text>
|
||||||
<TouchableOpacity onPress={() => setInboxVisible(false)} hitSlop={{top:8,bottom:8,left:8,right:8}}>
|
<TouchableOpacity onPress={() => setInboxVisible(false)} hitSlop={{top:8,bottom:8,left:8,right:8}}>
|
||||||
<Text style={{color:'#8888AA', fontSize:24}}>×</Text>
|
<Text style={{color:'#8888AA', fontSize:24}}>×</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
<MemoryBrowser />
|
{/* Aus aktuellem Chat: Spezial-Bubbles (memory/trigger/skill) kompakt
|
||||||
|
auflisten — neueste oben. Klick auf Memory oeffnet Detail-Modal. */}
|
||||||
|
{(() => {
|
||||||
|
const specials = messages
|
||||||
|
.filter(m => m.memorySaved || m.triggerCreated || m.skillCreated)
|
||||||
|
.slice().reverse();
|
||||||
|
if (specials.length === 0) {
|
||||||
|
return (
|
||||||
|
<View style={{padding:14, borderBottomWidth:1, borderBottomColor:'#1E1E2E'}}>
|
||||||
|
<Text style={{color:'#555570', fontSize:11, fontStyle:'italic'}}>
|
||||||
|
(keine Notizen-Bubbles im aktuellen Chat)
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<View style={{maxHeight:260, borderBottomWidth:1, borderBottomColor:'#1E1E2E'}}>
|
||||||
|
<Text style={{color:'#8888AA', fontSize:11, paddingHorizontal:14, paddingTop:8, paddingBottom:4, textTransform:'uppercase', letterSpacing:0.5}}>
|
||||||
|
Aus diesem Chat
|
||||||
|
</Text>
|
||||||
|
<ScrollView style={{paddingHorizontal:8}}>
|
||||||
|
{specials.map(m => {
|
||||||
|
if (m.memorySaved) {
|
||||||
|
const ms = m.memorySaved;
|
||||||
|
const action = ms.action || 'created';
|
||||||
|
const verb = action === 'updated' ? 'geändert' : action === 'deleted' ? 'gelöscht' : 'angelegt';
|
||||||
|
const dotColor = action === 'deleted' ? '#FF6B6B' : '#FFD60A';
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={m.id}
|
||||||
|
style={styles.inboxRow}
|
||||||
|
onPress={() => { if (ms.id && action !== 'deleted') { setInboxVisible(false); setMemoryDetailId(ms.id); } }}
|
||||||
|
disabled={!ms.id || action === 'deleted'}
|
||||||
|
>
|
||||||
|
<Text style={{fontSize:16}}>{'🧠'}</Text>
|
||||||
|
<View style={{flex:1}}>
|
||||||
|
<Text style={styles.inboxRowTitle} numberOfLines={1}>{ms.title}</Text>
|
||||||
|
<Text style={[styles.inboxRowMeta, {color: dotColor}]}>Memory · {verb} · {ms.type}</Text>
|
||||||
|
</View>
|
||||||
|
{ms.id && action !== 'deleted' ? <Text style={{color:'#0096FF', fontSize:14}}>›</Text> : null}
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (m.triggerCreated) {
|
||||||
|
const t = m.triggerCreated;
|
||||||
|
return (
|
||||||
|
<View key={m.id} style={styles.inboxRow}>
|
||||||
|
<Text style={{fontSize:16}}>{'⏰'}</Text>
|
||||||
|
<View style={{flex:1}}>
|
||||||
|
<Text style={styles.inboxRowTitle} numberOfLines={1}>{t.name}</Text>
|
||||||
|
<Text style={styles.inboxRowMeta}>Trigger · {t.type}{t.fires_at ? ` · ${t.fires_at.slice(0,16).replace('T',' ')}` : ''}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (m.skillCreated) {
|
||||||
|
const sk = m.skillCreated;
|
||||||
|
return (
|
||||||
|
<View key={m.id} style={styles.inboxRow}>
|
||||||
|
<Text style={{fontSize:16}}>{'🛠'}</Text>
|
||||||
|
<View style={{flex:1}}>
|
||||||
|
<Text style={styles.inboxRowTitle} numberOfLines={1}>{sk.name}</Text>
|
||||||
|
<Text style={styles.inboxRowMeta}>Skill · {sk.execution}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
<Text style={{color:'#8888AA', fontSize:11, paddingHorizontal:14, paddingTop:10, paddingBottom:4, textTransform:'uppercase', letterSpacing:0.5}}>
|
||||||
|
Alle Memories aus der DB
|
||||||
|
</Text>
|
||||||
|
<MemoryBrowser onOpenMemory={(id) => { setInboxVisible(false); setMemoryDetailId(id); }} />
|
||||||
</View>
|
</View>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
@@ -2181,6 +2265,25 @@ const styles = StyleSheet.create({
|
|||||||
playButtonText: {
|
playButtonText: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
},
|
},
|
||||||
|
inboxRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 10,
|
||||||
|
backgroundColor: '#1E1E2E',
|
||||||
|
padding: 10,
|
||||||
|
borderRadius: 6,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
inboxRowTitle: {
|
||||||
|
color: '#E0E0F0',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
inboxRowMeta: {
|
||||||
|
color: '#8888AA',
|
||||||
|
fontSize: 11,
|
||||||
|
marginTop: 1,
|
||||||
|
},
|
||||||
memoryAttachmentRow: {
|
memoryAttachmentRow: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|||||||
Reference in New Issue
Block a user