feedback api

This commit is contained in:
Krishav Raj Singh 2025-07-04 01:48:51 +05:30
parent a8fd7afcac
commit d506b0b63b
2 changed files with 66 additions and 22 deletions

31
backend/feedback/api.py Normal file
View File

@ -0,0 +1,31 @@
from pydantic import BaseModel, Field
from fastapi import APIRouter, HTTPException
from services.supabase import DBConnection
router = APIRouter(prefix="/feedback", tags=["feedback"])
class FeedbackRequest(BaseModel):
message_id: str = Field(..., description="ID of the message that is being rated")
is_good: bool = Field(..., description="True for good response, False for bad response")
feedback: str | None = Field(None, description="Optional free-form text feedback from the user")
db = DBConnection()
@router.post("/")
async def submit_feedback(request: FeedbackRequest):
try:
client = await db.client
feedback_data = {
'message_id': request.message_id,
'is_good': request.is_good,
'feedback': request.feedback
}
feedback_result = await client.table('feedback').insert(feedback_data).execute()
if not feedback_result.data:
raise HTTPException(status_code=500, detail="Failed to submit feedback")
return {"success": True}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View File

@ -4,7 +4,9 @@ import { ThumbsDown, ThumbsUp } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import { Textarea } from "../ui/textarea"; import { Textarea } from "../ui/textarea";
import { toast } from "sonner"; import { toast } from "sonner";
import { apiClient } from '@/lib/api-client'; import { backendApi } from '@/lib/api-client';
type SubmitStatus = 'idle' | 'submitting' | 'success' | 'error';
interface FeedbackProps { interface FeedbackProps {
messageId: string; messageId: string;
@ -14,7 +16,7 @@ export default function Feedback({ messageId }: FeedbackProps) {
const [open, setOpen] = useState<boolean>(false); const [open, setOpen] = useState<boolean>(false);
const [responseIsGood, setResponseIsGood] = useState<boolean | null>(null); const [responseIsGood, setResponseIsGood] = useState<boolean | null>(null);
const [feedback, setFeedback] = useState<string>(''); const [feedback, setFeedback] = useState<string>('');
const [submitting, setSubmitting] = useState(false); const [submitStatus, setSubmitStatus] = useState<SubmitStatus>('idle');
const handleClick = (isGood: boolean) => { const handleClick = (isGood: boolean) => {
setResponseIsGood(isGood); setResponseIsGood(isGood);
@ -23,18 +25,25 @@ export default function Feedback({ messageId }: FeedbackProps) {
const handleSubmit = async () => { const handleSubmit = async () => {
if (responseIsGood === null) return; if (responseIsGood === null) return;
setSubmitting(true); setSubmitStatus('submitting');
const { success } = await apiClient.post('/api/feedback', {
message_id: messageId, try {
response_is_good: responseIsGood, const { success } = await backendApi.post('/feedback/', {
comment: feedback.trim() || null, message_id: messageId,
}); is_good: responseIsGood,
setSubmitting(false); feedback: feedback.trim() || null,
if (success) { });
toast.success('Feedback submitted - thank you!'); setSubmitStatus('success');
setOpen(false); if (success) {
setFeedback(''); toast.success('Feedback submitted - thank you!');
setResponseIsGood(null); setOpen(false);
setFeedback('');
setSubmitStatus('success');
}
} catch (error) {
console.error('Failed to submit feedback:', error);
setSubmitStatus('error');
toast.error('Failed to submit feedback');
} }
}; };
@ -45,7 +54,7 @@ export default function Feedback({ messageId }: FeedbackProps) {
variant="ghost" variant="ghost"
onClick={() => handleClick(true)} onClick={() => handleClick(true)}
> >
<ThumbsUp className="h-4 w-4" /> <ThumbsUp className={`h-4 w-4 ${submitStatus === 'success' && responseIsGood ? 'fill-white' : ''}`} />
<span className="sr-only">Good response</span> <span className="sr-only">Good response</span>
</Button> </Button>
<Button <Button
@ -54,19 +63,19 @@ export default function Feedback({ messageId }: FeedbackProps) {
variant="ghost" variant="ghost"
onClick={() => handleClick(false)} onClick={() => handleClick(false)}
> >
<ThumbsDown className="h-4 w-4" /> <ThumbsDown className={`h-4 w-4 ${submitStatus === 'success' && responseIsGood === false ? 'fill-white' : ''}`} />
<span className="sr-only">Bad response</span> <span className="sr-only">Bad response</span>
</Button> </Button>
<Dialog open={open} onOpenChange={setOpen}> <Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="max-w-md"> <DialogContent className="max-w-md">
<DialogHeader> <DialogHeader>
<DialogTitle>{responseIsGood ? 'Good response' : 'Bad response'}</DialogTitle> <DialogTitle>Feedback</DialogTitle>
</DialogHeader> </DialogHeader>
<span <span
className="text-sm text-muted-foreground" className="text-sm text-muted-foreground"
> >
{`What was ${responseIsGood === false ? 'un' : ''}satisifying about this response?`} {`What was ${responseIsGood === false ? 'un' : ''}satisfying about this response?`}
</span> </span>
<Textarea <Textarea
className="resize-none my-2" className="resize-none my-2"
@ -76,10 +85,14 @@ export default function Feedback({ messageId }: FeedbackProps) {
/> />
<DialogFooter className="gap-2"> <DialogFooter className="gap-2">
<DialogClose asChild> <DialogClose asChild>
<Button variant="outline">Cancel</Button> <Button variant="outline">
Cancel
<span className="sr-only">Cancel feedback</span>
</Button>
</DialogClose> </DialogClose>
<Button onClick={handleSubmit} disabled={submitting || responseIsGood === null}> <Button onClick={handleSubmit} disabled={submitStatus === 'submitting' || responseIsGood === null}>
{submitting ? 'Submitting...' : 'Submit'} {submitStatus === 'submitting' ? 'Submitting...' : 'Submit'}
<span className="sr-only">Submit feedback</span>
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>