feat(app+brain): App-Bugfixes + Skill-Mgmt-Tools + Voice-Speed persistent + Skill-Browser
App-Bugs:
- Trigger-Liste war leer: brainApi.listTriggers() cast'te {triggers: [...]}
direkt als Array, t.sort() warf — TriggerBrowser blieb leer. Fix: unwrap.
- GPS-Tracking startete erst bei SettingsScreen-Mount, nicht beim App-Boot.
Wenn Stefan direkt in den Chat ging, blieb GPS aus. Fix: restoreFromStorage()
in App.tsx useEffect.
- Text in Chat-Bubbles nicht markierbar / kein Copy-Mechanismus: Bubble jetzt
Pressable mit onLongPress + neues ⎘-Icon in Status-Row → openBubbleActions().
Alert-Menu mit "Ganzen Text teilen" + pro extrahierte URL/Mail/Tel eine
eigene Option. Share.share() — keine neuen Native-Deps noetig.
Brain — Skill-Mgmt:
- ARIA legte beim Skill-Umbau neue Versionen mit Suffix an (Skill-Friedhof),
weil sie kein Update/Delete-Tool kannte. Zwei neue META_TOOLS in agent.py:
skill_update (kann entry_code, readme, pip_packages, args, description,
active patchen — venv wird bei pip_packages-Aenderung rebuilt) + skill_delete.
- skills.py update_skill um entry_code/readme/pip_packages erweitert,
venv-Rebuild bei pip-Aenderung.
Bridge — Voice-Speed persistent:
- _next_speed_override war pro-Request-Override ohne Persistenz. Bei
Diagnostic-Chats / Trigger-Replies ohne vorherigen App-Chat fiel der Speed
auf 1.0 zurueck, ebenso nach Bridge-Restart. Jetzt: _persistent_xtts_speed
aus voice_config.json (xttsSpeed), wird nach jedem App-chat mit speed
autopersistiert. TTS-Generation faellt zurueck: per-Request > persistent > 1.0.
App — Feature 6:
- SkillBrowser.tsx: Liste aller Skills, Toggle aktiv/inaktiv, Detail-Modal
mit Args-Inputs, Ausfuehren mit Live-stdout/stderr, Logs der letzten 20
Runs, Loeschen. Settings-Sektion "Skills" (🛠️) zwischen Trigger und
Protokoll. brainApi.listSkills/getSkill/runSkill/updateSkill/deleteSkill/
getSkillLogs ergaenzt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,8 @@ import {
|
||||
AppState,
|
||||
NativeModules,
|
||||
Alert,
|
||||
Pressable,
|
||||
Share,
|
||||
} from 'react-native';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import RNFS from 'react-native-fs';
|
||||
@@ -1987,7 +1989,7 @@ const ChatScreen: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
<Pressable
|
||||
style={[styles.messageBubble, isUser ? styles.userBubble : styles.ariaBubble, searchHighlightStyle]}
|
||||
onLayout={e => {
|
||||
// Echte Hoehe in Cache speichern — Pre-Scroll der Suche nutzt
|
||||
@@ -1995,6 +1997,9 @@ const ChatScreen: React.FC = () => {
|
||||
// unbekannten Items faellt's auf AVG_BUBBLE_HEIGHT zurueck.
|
||||
itemHeights.current.set(item.id, e.nativeEvent.layout.height);
|
||||
}}
|
||||
onLongPress={() => openBubbleActions(item)}
|
||||
delayLongPress={500}
|
||||
android_ripple={null}
|
||||
>
|
||||
{/* Anhang-Vorschau */}
|
||||
{item.attachments?.map((att, idx) => (
|
||||
@@ -2125,6 +2130,15 @@ const ChatScreen: React.FC = () => {
|
||||
) : null}
|
||||
<View style={styles.statusRow}>
|
||||
<Text style={styles.timestamp}>{time}</Text>
|
||||
{item.text.length > 0 ? (
|
||||
<TouchableOpacity
|
||||
hitSlop={{top:6,bottom:6,left:6,right:6}}
|
||||
onPress={() => openBubbleActions(item)}
|
||||
accessibilityLabel="Aktionen"
|
||||
>
|
||||
<Text style={styles.bubbleCopyIcon}>{'⎘'}</Text>
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
{isUser && item.deliveryStatus ? (
|
||||
item.deliveryStatus === 'failed' && item.clientMsgId ? (
|
||||
<TouchableOpacity
|
||||
@@ -2148,7 +2162,58 @@ const ChatScreen: React.FC = () => {
|
||||
)
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
// Extrahiert kopierbare Items aus dem Bubble-Text (URLs, Mails, Telefon).
|
||||
// Wird vom Long-Press/Copy-Menu genutzt damit Stefan den einzelnen Wert
|
||||
// teilen kann ohne den umliegenden Text mitzunehmen.
|
||||
const extractCopyables = (text: string): { label: string; value: string }[] => {
|
||||
const items: { label: string; value: string }[] = [];
|
||||
const urlRe = /https?:\/\/[^\s<>"']+/gi;
|
||||
const mailRe = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
||||
const telRe = /(?:\+?\d[\d ()/-]{6,}\d)/g;
|
||||
const seen = new Set<string>();
|
||||
const push = (label: string, value: string) => {
|
||||
const trimmed = value.trim().replace(/[,;.)\]}>]+$/g, '');
|
||||
if (!trimmed || seen.has(trimmed)) return;
|
||||
seen.add(trimmed);
|
||||
items.push({ label, value: trimmed });
|
||||
};
|
||||
(text.match(urlRe) || []).forEach(u => push('URL', u));
|
||||
(text.match(mailRe) || []).forEach(m => push('E-Mail', m));
|
||||
(text.match(telRe) || []).forEach(t => push('Telefon', t));
|
||||
return items.slice(0, 5); // max 5 items, mehr wird unleserlich
|
||||
};
|
||||
|
||||
// Long-Press oder ⎘-Icon auf einer Bubble. Zeigt einen Alert mit
|
||||
// "Text teilen" (= System-Share-Sheet, dort gibt's auch Zwischenablage)
|
||||
// sowie pro extrahierte URL/E-Mail/Telefonnummer eine Option um
|
||||
// gezielt nur dieses Item zu teilen.
|
||||
const openBubbleActions = (item: ChatMessage) => {
|
||||
const text = showSystemHints ? item.text : stripSystemHints(item.text);
|
||||
if (!text) return;
|
||||
const copyables = extractCopyables(text);
|
||||
const buttons: any[] = [
|
||||
{
|
||||
text: '📋 Ganzen Text teilen',
|
||||
onPress: () => Share.share({ message: text }).catch(() => {}),
|
||||
},
|
||||
];
|
||||
for (const c of copyables) {
|
||||
buttons.push({
|
||||
text: `📎 ${c.label}: ${c.value.slice(0, 32)}${c.value.length > 32 ? '…' : ''}`,
|
||||
onPress: () => Share.share({ message: c.value }).catch(() => {}),
|
||||
});
|
||||
}
|
||||
buttons.push({ text: 'Abbrechen', style: 'cancel' });
|
||||
Alert.alert(
|
||||
'Bubble-Aktionen',
|
||||
copyables.length > 0
|
||||
? 'Was moechtest du teilen / kopieren?'
|
||||
: 'Text in System-Share-Sheet oeffnen (dort "In Zwischenablage" verfuegbar).',
|
||||
buttons,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3126,6 +3191,12 @@ const styles = StyleSheet.create({
|
||||
fontSize: 12,
|
||||
color: '#FF6B6B',
|
||||
},
|
||||
bubbleCopyIcon: {
|
||||
fontSize: 13,
|
||||
color: '#8888AA',
|
||||
marginLeft: 6,
|
||||
opacity: 0.7,
|
||||
},
|
||||
fullscreenOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0,0,0,0.95)',
|
||||
|
||||
Reference in New Issue
Block a user