view feedback on refresh

This commit is contained in:
Krishav Raj Singh 2025-07-04 00:33:34 +05:30
parent 01605a99bf
commit 0fefd5857a
10 changed files with 118 additions and 7 deletions

View File

@ -30,7 +30,7 @@ async def submit_feedback(request: FeedbackRequest, user_id: str = Depends(get_c
'feedback': request.feedback
}
feedback_result = await client.table('feedback').insert(feedback_data).execute()
feedback_result = await client.table('feedback').upsert(feedback_data, on_conflict='message_id').execute()
if not feedback_result.data:
raise HTTPException(status_code=500, detail="Failed to submit feedback")

View File

@ -0,0 +1,66 @@
create table "public"."feedback" (
"id" uuid not null default gen_random_uuid(),
"created_at" timestamp with time zone not null default now(),
"is_good" boolean not null,
"message_id" uuid,
"feedback" text
);
alter table "public"."feedback" enable row level security;
CREATE UNIQUE INDEX feedback_message_id_key ON public.feedback USING btree (message_id);
CREATE UNIQUE INDEX feedback_pkey ON public.feedback USING btree (id);
alter table "public"."feedback" add constraint "feedback_pkey" PRIMARY KEY using index "feedback_pkey";
alter table "public"."feedback" add constraint "feedback_message_id_fkey" FOREIGN KEY (message_id) REFERENCES messages(message_id) not valid;
alter table "public"."feedback" validate constraint "feedback_message_id_fkey";
alter table "public"."feedback" add constraint "feedback_message_id_key" UNIQUE using index "feedback_message_id_key";
grant delete on table "public"."feedback" to "anon";
grant insert on table "public"."feedback" to "anon";
grant references on table "public"."feedback" to "anon";
grant select on table "public"."feedback" to "anon";
grant trigger on table "public"."feedback" to "anon";
grant truncate on table "public"."feedback" to "anon";
grant update on table "public"."feedback" to "anon";
grant delete on table "public"."feedback" to "authenticated";
grant insert on table "public"."feedback" to "authenticated";
grant references on table "public"."feedback" to "authenticated";
grant select on table "public"."feedback" to "authenticated";
grant trigger on table "public"."feedback" to "authenticated";
grant truncate on table "public"."feedback" to "authenticated";
grant update on table "public"."feedback" to "authenticated";
grant delete on table "public"."feedback" to "service_role";
grant insert on table "public"."feedback" to "service_role";
grant references on table "public"."feedback" to "service_role";
grant select on table "public"."feedback" to "service_role";
grant trigger on table "public"."feedback" to "service_role";
grant truncate on table "public"."feedback" to "service_role";
grant update on table "public"."feedback" to "service_role";

View File

@ -79,6 +79,7 @@ export function useThreadData(threadId: string, projectId: string): UseThreadDat
thread_id: msg.thread_id || threadId,
type: (msg.type || 'system') as UnifiedMessage['type'],
is_llm_message: Boolean(msg.is_llm_message),
user_feedback: msg.user_feedback ?? null,
content: msg.content || '',
metadata: msg.metadata || '{}',
created_at: msg.created_at || new Date().toISOString(),
@ -152,6 +153,7 @@ export function useThreadData(threadId: string, projectId: string): UseThreadDat
thread_id: msg.thread_id || threadId,
type: (msg.type || 'system') as UnifiedMessage['type'],
is_llm_message: Boolean(msg.is_llm_message),
user_feedback: msg.user_feedback ?? null,
content: msg.content || '',
metadata: msg.metadata || '{}',
created_at: msg.created_at || new Date().toISOString(),

View File

@ -20,6 +20,7 @@ export interface ApiMessageType extends BaseApiMessageType {
metadata?: string;
created_at?: string;
updated_at?: string;
user_feedback?: boolean | null;
}
export interface StreamingToolCall {

View File

@ -48,6 +48,7 @@ interface ApiMessageType extends BaseApiMessageType {
avatar?: string;
avatar_color?: string;
};
user_feedback?: boolean | null;
}
interface StreamingToolCall {
@ -401,6 +402,7 @@ export default function ThreadPage({
updated_at: msg.updated_at || new Date().toISOString(),
agent_id: (msg as any).agent_id,
agents: (msg as any).agents,
user_feedback: msg.user_feedback ?? null,
}));
setMessages(unifiedMessages);

View File

@ -904,7 +904,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
return (
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<Feedback messageId={messageId} />
<Feedback messageId={messageId} initialFeedback={firstAssistant?.user_feedback ?? null} />
</div>
);
})()}

View File

@ -10,13 +10,14 @@ type SubmitStatus = 'idle' | 'submitting' | 'success' | 'error';
interface FeedbackProps {
messageId: string;
initialFeedback?: boolean | null;
}
export default function Feedback({ messageId }: FeedbackProps) {
export default function Feedback({ messageId, initialFeedback = null }: FeedbackProps) {
const [open, setOpen] = useState<boolean>(false);
const [responseIsGood, setResponseIsGood] = useState<boolean | null>(null);
const [responseIsGood, setResponseIsGood] = useState<boolean | null>(initialFeedback);
const [feedback, setFeedback] = useState<string>('');
const [submitStatus, setSubmitStatus] = useState<SubmitStatus>('idle');
const [submitStatus, setSubmitStatus] = useState<SubmitStatus>(initialFeedback !== null ? 'success' : 'idle');
const handleClick = (isGood: boolean) => {
setResponseIsGood(isGood);

View File

@ -25,6 +25,7 @@ export interface UnifiedMessage {
avatar?: string;
avatar_color?: string;
}; // Agent information from join
user_feedback?: boolean | null; // Feedback provided by current user: true (liked), false (disliked), null / undefined (no feedback)
}
// Helper type for parsed content - structure depends on message.type

View File

@ -29,6 +29,7 @@ interface ApiMessageType {
avatar?: string;
avatar_color?: string;
};
user_feedback?: boolean | null;
}
// Define the structure returned by the hook
@ -68,6 +69,7 @@ const mapApiMessagesToUnified = (
metadata: msg.metadata || '{}',
created_at: msg.created_at || new Date().toISOString(),
updated_at: msg.updated_at || new Date().toISOString(),
user_feedback: msg.user_feedback ?? null,
agent_id: (msg as any).agent_id,
agents: (msg as any).agents,
}));

View File

@ -76,6 +76,11 @@ export type Message = {
};
};
export interface MessageWithFeedback extends Message {
message_id: string;
user_feedback?: boolean | null;
}
export type AgentRun = {
id: string;
thread_id: string;
@ -574,10 +579,10 @@ export const addUserMessage = async (
}
};
export const getMessages = async (threadId: string): Promise<Message[]> => {
export const getMessages = async (threadId: string): Promise<MessageWithFeedback[]> => {
const supabase = createClient();
let allMessages: Message[] = [];
let allMessages: MessageWithFeedback[] = [];
let from = 0;
const batchSize = 1000;
let hasMore = true;
@ -614,6 +619,37 @@ export const getMessages = async (threadId: string): Promise<Message[]> => {
}
}
try {
const messageIds = allMessages
.filter((m: Message) => m.type === 'assistant')
.map((m: any) => m.message_id)
.filter((id) => Boolean(id));
if (messageIds.length > 0) {
const { data: feedbackData, error: feedbackError } = await supabase
.from('feedback')
.select('message_id, is_good')
.in('message_id', messageIds);
if (feedbackError) {
console.error('Error fetching feedback data:', feedbackError);
}
const feedback = Object.fromEntries(
feedbackData?.map((feedback: { message_id: string; is_good: boolean }) => [feedback.message_id, feedback.is_good]) ?? []
);
// Attach feedback to messages
allMessages = allMessages.map((msg: MessageWithFeedback) => ({
...msg,
user_feedback: feedback[msg.message_id] ?? null,
}));
}
} catch (feedbackAttachError) {
console.error('Failed to attach feedback metadata to messages:', feedbackAttachError);
}
console.log('[API] Messages fetched count:', allMessages.length);
return allMessages;