Origem: D:/Documents/Github/LocalAiApp Arquivos omitidos (segredos, bancos de dados, logs, >50 MB): 0
128 lines
3.8 KiB
TypeScript
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',
|
|
},
|
|
});
|