Files
LocalMind/components/chat/ThinkingBlock.tsx
T
evandro e08189f640 wip: snapshot das alterações não commitadas (2026-10-20)
Origem: D:/Documents/Github/LocalAiApp
Arquivos omitidos (segredos, bancos de dados, logs, >50 MB): 0
2026-10-20 00:48:09 -03:00

128 lines
3.8 KiB
TypeScript

import { useThemeColor } from '@/hooks/useThemeColor';
import { BrainCircuit, ChevronDown, ChevronRight } from 'lucide-react-native';
import React, { useEffect, useState } from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import Animated, {
useAnimatedStyle,
useSharedValue,
withRepeat,
withSequence,
withTiming
} from 'react-native-reanimated';
interface ThinkingBlockProps {
content: string;
isComplete: boolean;
isStreaming?: boolean;
}
export function ThinkingBlock({ content, isComplete, isStreaming }: ThinkingBlockProps) {
const colors = useThemeColor();
// Auto-expand while streaming/thinking, auto-collapse when complete
const [isExpanded, setIsExpanded] = useState(!isComplete);
// Auto collapse when it completes streaming
useEffect(() => {
if (isComplete && !isStreaming) {
setIsExpanded(false);
} else if (!isComplete && isStreaming) {
setIsExpanded(true);
}
}, [isComplete, isStreaming]);
// Pulse animation value
const pulseOpacity = useSharedValue(1);
useEffect(() => {
if (!isComplete && isStreaming) {
pulseOpacity.value = withRepeat(
withSequence(
withTiming(0.4, { duration: 800 }),
withTiming(1, { duration: 800 })
),
-1, // infinite
true // reverse
);
} else {
pulseOpacity.value = withTiming(1, { duration: 300 });
}
}, [isComplete, isStreaming, pulseOpacity]);
const animatedHeaderStyle = useAnimatedStyle(() => {
return {
opacity: pulseOpacity.value,
};
});
if (!content && isComplete) {
return null;
}
return (
<View style={[styles.container, { borderColor: colors.border }]}>
<TouchableOpacity
style={styles.header}
activeOpacity={0.7}
onPress={() => setIsExpanded(!isExpanded)}
>
<Animated.View style={[styles.titleRow, animatedHeaderStyle]}>
<BrainCircuit size={16} color={colors.textMuted} />
<Text style={[styles.titleText, { color: colors.textMuted }]}>
{!isComplete && isStreaming ? 'Pensando...' : 'Processo de Raciocínio'}
</Text>
</Animated.View>
{isExpanded ? (
<ChevronDown size={18} color={colors.textMuted} />
) : (
<ChevronRight size={18} color={colors.textMuted} />
)}
</TouchableOpacity>
{isExpanded && content.length > 0 && (
<View style={[styles.contentBlock, { borderTopColor: colors.border }]}>
<Text style={[styles.contentText, { color: colors.textMuted }]}>
{content}
</Text>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
borderWidth: 1,
borderRadius: 8,
marginVertical: 8,
overflow: 'hidden',
width: '100%',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 12,
paddingVertical: 10,
},
titleRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 8,
},
titleText: {
fontSize: 13,
fontWeight: '600',
},
contentBlock: {
paddingHorizontal: 12,
paddingVertical: 10,
borderTopWidth: 1,
},
contentText: {
fontSize: 14,
lineHeight: 20,
fontFamily: 'monospace',
},
});