import React, { useState } from 'react'; import { View, Text, StyleSheet, ScrollView, TextInput, TouchableOpacity, ActivityIndicator, } from 'react-native'; import { COLORS, FONT_SIZE, SPACING, RADIUS, TEXT, SHADOWS } from '../../../theme'; import { useTheme } from '../../../theme'; import { chatAPI } from '../../../services/api'; interface TestResult { question: string; response: string; confidence_score: number; sources: { document_name: string; chunk_text: string; score: number }[]; model_used: string; } export function TestingTab({ chatbotId }: { chatbotId: string }) { const { theme } = useTheme(); const [questions, setQuestions] = useState(['']); const [results, setResults] = useState([]); const [expandedIdx, setExpandedIdx] = useState(null); const [running, setRunning] = useState(false); const [error, setError] = useState(''); const addQuestion = () => { if (questions.length < 10) setQuestions(prev => [...prev, '']); }; const updateQuestion = (idx: number, val: string) => { setQuestions(prev => prev.map((q, i) => (i === idx ? val : q))); }; const removeQuestion = (idx: number) => { setQuestions(prev => prev.filter((_, i) => i !== idx)); }; const runTests = async () => { const valid = questions.map(q => q.trim()).filter(Boolean); if (!valid.length) return; setRunning(true); setError(''); setResults([]); setExpandedIdx(null); try { const data = await chatAPI.test(chatbotId, valid); setResults(data); setExpandedIdx(0); } catch (e: any) { setError(e?.response?.data?.detail ?? 'Test failed. Please try again.'); } finally { setRunning(false); } }; const confidenceColor = (score: number) => { if (score >= 0.7) return COLORS.success; if (score >= 0.4) return COLORS.warning; return COLORS.error; }; return ( Test Questions Enter up to 10 questions to test how your chatbot responds. {questions.map((q, idx) => ( {idx + 1}. updateQuestion(idx, val)} placeholder="Ask a question..." placeholderTextColor={theme.placeholder} /> {questions.length > 1 && ( removeQuestion(idx)} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}> )} ))} {questions.length < 10 && ( + Add question )} q.trim())) && styles.runBtnDisabled, ]} onPress={runTests} disabled={running || !questions.some(q => q.trim())}> {running ? : ▶ Run Tests } {error ? ( {error} ) : null} {results.length > 0 && ( <> {results.length} RESULT{results.length !== 1 ? 'S' : ''} {results.map((r, idx) => ( setExpandedIdx(expandedIdx === idx ? null : idx)}> {Math.round(r.confidence_score * 100)}% {r.question} {expandedIdx === idx ? '▲' : '▼'} {expandedIdx === idx && ( {r.response} {r.sources.length > 0 && ( <> SOURCES {r.sources.map((src, si) => ( {src.document_name} · {Math.round(src.score * 100)}% {src.chunk_text} ))} )} Model: {r.model_used} )} ))} )} ); } const styles = StyleSheet.create({ scroll: { flex: 1 }, scrollContent: { padding: SPACING.lg, gap: SPACING.lg, paddingBottom: SPACING.xxxl }, card: { borderRadius: RADIUS.xl, borderWidth: 1, padding: SPACING.lg, gap: SPACING.md, }, cardTitle: { ...TEXT.h4 }, cardDesc: { ...TEXT.small }, questionRow: { flexDirection: 'row', alignItems: 'center', gap: SPACING.sm }, questionNum: { width: 18, fontSize: FONT_SIZE.sm, textAlign: 'right' }, questionInput: { flex: 1, borderWidth: 1.5, borderRadius: RADIUS.md, paddingHorizontal: SPACING.md, paddingVertical: SPACING.sm, fontSize: FONT_SIZE.md, }, removeBtn: { color: '#ccc', fontSize: FONT_SIZE.md, paddingHorizontal: SPACING.xs }, actions: { flexDirection: 'row', alignItems: 'center', marginTop: SPACING.xs }, addBtn: { fontSize: FONT_SIZE.sm, fontWeight: '600' }, runBtn: { marginLeft: 'auto', flexDirection: 'row', alignItems: 'center', gap: SPACING.xs, borderRadius: RADIUS.md, paddingVertical: SPACING.sm, paddingHorizontal: SPACING.lg, minWidth: 100, justifyContent: 'center', }, runBtnDisabled: { opacity: 0.5 }, runBtnText: { color: COLORS.white, fontSize: FONT_SIZE.sm, fontWeight: '700' }, error: { fontSize: FONT_SIZE.sm }, resultsLabel: { fontSize: FONT_SIZE.xs, fontWeight: '700', letterSpacing: 0.8 }, resultCard: { borderRadius: RADIUS.xl, borderWidth: 1, overflow: 'hidden' }, resultHeader: { flexDirection: 'row', alignItems: 'center', padding: SPACING.md, gap: SPACING.sm }, confBadge: { borderRadius: RADIUS.sm, paddingVertical: 3, paddingHorizontal: SPACING.sm }, confText: { fontSize: FONT_SIZE.xs, fontWeight: '800' }, resultQuestion: { flex: 1, ...TEXT.smallM }, chevron: { fontSize: FONT_SIZE.sm }, resultBody: { borderTopWidth: 1, padding: SPACING.md, gap: SPACING.sm }, responseText: { ...TEXT.body, lineHeight: 22 }, sourcesLabel: { fontSize: FONT_SIZE.xs, fontWeight: '700', letterSpacing: 0.8, marginTop: SPACING.xs }, sourceChip: { borderRadius: RADIUS.md, borderWidth: 1, padding: SPACING.sm, gap: 3, }, sourceName: { fontSize: FONT_SIZE.xs, fontWeight: '600' }, sourceChunk: { fontSize: FONT_SIZE.xs, lineHeight: 16 }, modelLabel: { fontSize: FONT_SIZE.xs, marginTop: SPACING.xs }, });