mirror of https://github.com/kortix-ai/suna.git
feedback api
This commit is contained in:
parent
a8fd7afcac
commit
d506b0b63b
|
@ -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))
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue