Spaces:
Running
Running
import React, { useState, useEffect } from 'react'; | |
import { | |
Box, | |
Paper, | |
Typography, | |
IconButton, | |
Alert, | |
Grid | |
} from '@mui/material'; | |
import styled from '@emotion/styled'; | |
import { saveFeedback } from '../../services/search'; | |
import { useAuth } from '../../hooks/useAuth'; | |
const ResultCard = styled(Paper)(({ theme }) => ({ | |
padding: theme.spacing(3), | |
height: '100%', | |
display: 'flex', | |
flexDirection: 'column', | |
position: 'relative', | |
})); | |
const EmojiButton = styled(IconButton)(({ theme }) => ({ | |
fontSize: '1.5rem', | |
padding: theme.spacing(1), | |
})); | |
const SearchResults = ({ results, query, isLoading, error }) => { | |
const { user, authTokens, userType } = useAuth(); | |
const [shuffledResults, setShuffledResults] = useState([]); | |
const [feedbackStatus, setFeedbackStatus] = useState({}); | |
const [savingFeedback, setSavingFeedback] = useState(false); | |
// Shuffle and limit to 2 results when results change | |
useEffect(() => { | |
if (results && results.length) { | |
// Get one result from each model type if possible | |
const bgeResult = results.find(r => r.model_type?.includes('BGE')); | |
const uaeResult = results.find(r => r.model_type?.includes('UAE')); | |
let selectedResults = []; | |
if (bgeResult && uaeResult) { | |
selectedResults = [bgeResult, uaeResult]; | |
} else { | |
// If we don't have both models, take top 2 (or all if less than 2) | |
selectedResults = results.slice(0, 2); | |
} | |
// Randomly shuffle the order | |
const shuffled = [...selectedResults].sort(() => Math.random() - 0.5); | |
// Assign labels (A and B) | |
setShuffledResults(shuffled.map((result, index) => ({ | |
...result, | |
resultLabel: index === 0 ? 'A' : 'B' | |
}))); | |
// Reset feedback status | |
setFeedbackStatus({}); | |
} else { | |
setShuffledResults([]); | |
} | |
}, [results]); | |
if (error) { | |
return ( | |
<Alert severity="error" sx={{ my: 2 }}> | |
{error} | |
</Alert> | |
); | |
} | |
if (!results || results.length === 0) { | |
return ( | |
<Typography variant="body1" color="textSecondary" align="center" sx={{ my: 4 }}> | |
No results found. Try a different query. | |
</Typography> | |
); | |
} | |
const handleReaction = async (result, reaction) => { | |
// Prevent multiple submissions | |
if (feedbackStatus[result.resultLabel] || savingFeedback) return; | |
setSavingFeedback(true); | |
try { | |
// Prepare feedback data | |
const feedbackData = { | |
user_type: userType, // Use userType from auth context instead of hardcoded 'user' | |
username: user || 'anonymous', | |
query: query || '', | |
retrieved_text: result.content || result.text || JSON.stringify(result), | |
model_type: result.model_type || 'unknown', | |
result_label: result.resultLabel || '', | |
reaction: reaction, // Save the emoji directly instead of converting to text | |
confidence_score: result.similarity || 0, // Send the model confidence score | |
}; | |
// Save the feedback | |
await saveFeedback(feedbackData, authTokens.access); | |
// Update UI to show feedback was saved | |
setFeedbackStatus(prev => ({ | |
...prev, | |
[result.resultLabel]: { status: 'success', reaction } | |
})); | |
} catch (err) { | |
console.error("Feedback submission error:", err); | |
setFeedbackStatus(prev => ({ | |
...prev, | |
[result.resultLabel]: { status: 'error', message: 'Failed to save feedback' } | |
})); | |
} finally { | |
setSavingFeedback(false); | |
} | |
}; | |
return ( | |
<Box> | |
<Typography variant="h6" gutterBottom> | |
Search Results | |
</Typography> | |
<Grid container spacing={3} sx={{ mb: 4 }}> | |
{shuffledResults.map((result) => ( | |
<Grid item xs={12} md={6} key={result.resultLabel}> | |
<ResultCard elevation={3}> | |
<Typography variant="h6" gutterBottom> | |
Result {result.resultLabel} | |
</Typography> | |
<Typography variant="body1" paragraph sx={{ flexGrow: 1 }}> | |
{result.text} | |
</Typography> | |
<Box mt={2}> | |
{feedbackStatus[result.resultLabel]?.status === 'success' ? ( | |
<Typography variant="body2" color="success.main" align="center"> | |
Thank you for your feedback! You selected {feedbackStatus[result.resultLabel].reaction} | |
</Typography> | |
) : feedbackStatus[result.resultLabel]?.status === 'error' ? ( | |
<Typography variant="body2" color="error" align="center"> | |
{feedbackStatus[result.resultLabel].message} | |
</Typography> | |
) : ( | |
<Box display="flex" justifyContent="center" gap={2}> | |
<EmojiButton | |
onClick={() => handleReaction(result, 'π')} | |
disabled={savingFeedback} | |
aria-label="Good result" | |
> | |
π | |
</EmojiButton> | |
<EmojiButton | |
onClick={() => handleReaction(result, 'π€·ββοΈ')} | |
disabled={savingFeedback} | |
aria-label="Somewhat relevant" | |
> | |
π€·ββοΈ | |
</EmojiButton> | |
<EmojiButton | |
onClick={() => handleReaction(result, 'π')} | |
disabled={savingFeedback} | |
aria-label="Not relevant" | |
> | |
π | |
</EmojiButton> | |
</Box> | |
)} | |
</Box> | |
</ResultCard> | |
</Grid> | |
))} | |
</Grid> | |
</Box> | |
); | |
}; | |
export default SearchResults; |