mirror of https://github.com/kortix-ai/suna.git
view feedback on refresh
This commit is contained in:
parent
01605a99bf
commit
0fefd5857a
|
@ -30,7 +30,7 @@ async def submit_feedback(request: FeedbackRequest, user_id: str = Depends(get_c
|
||||||
'feedback': request.feedback
|
'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:
|
if not feedback_result.data:
|
||||||
raise HTTPException(status_code=500, detail="Failed to submit feedback")
|
raise HTTPException(status_code=500, detail="Failed to submit feedback")
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,7 @@ export function useThreadData(threadId: string, projectId: string): UseThreadDat
|
||||||
thread_id: msg.thread_id || threadId,
|
thread_id: msg.thread_id || threadId,
|
||||||
type: (msg.type || 'system') as UnifiedMessage['type'],
|
type: (msg.type || 'system') as UnifiedMessage['type'],
|
||||||
is_llm_message: Boolean(msg.is_llm_message),
|
is_llm_message: Boolean(msg.is_llm_message),
|
||||||
|
user_feedback: msg.user_feedback ?? null,
|
||||||
content: msg.content || '',
|
content: msg.content || '',
|
||||||
metadata: msg.metadata || '{}',
|
metadata: msg.metadata || '{}',
|
||||||
created_at: msg.created_at || new Date().toISOString(),
|
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,
|
thread_id: msg.thread_id || threadId,
|
||||||
type: (msg.type || 'system') as UnifiedMessage['type'],
|
type: (msg.type || 'system') as UnifiedMessage['type'],
|
||||||
is_llm_message: Boolean(msg.is_llm_message),
|
is_llm_message: Boolean(msg.is_llm_message),
|
||||||
|
user_feedback: msg.user_feedback ?? null,
|
||||||
content: msg.content || '',
|
content: msg.content || '',
|
||||||
metadata: msg.metadata || '{}',
|
metadata: msg.metadata || '{}',
|
||||||
created_at: msg.created_at || new Date().toISOString(),
|
created_at: msg.created_at || new Date().toISOString(),
|
||||||
|
|
|
@ -20,6 +20,7 @@ export interface ApiMessageType extends BaseApiMessageType {
|
||||||
metadata?: string;
|
metadata?: string;
|
||||||
created_at?: string;
|
created_at?: string;
|
||||||
updated_at?: string;
|
updated_at?: string;
|
||||||
|
user_feedback?: boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StreamingToolCall {
|
export interface StreamingToolCall {
|
||||||
|
|
|
@ -48,6 +48,7 @@ interface ApiMessageType extends BaseApiMessageType {
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
avatar_color?: string;
|
avatar_color?: string;
|
||||||
};
|
};
|
||||||
|
user_feedback?: boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface StreamingToolCall {
|
interface StreamingToolCall {
|
||||||
|
@ -401,6 +402,7 @@ export default function ThreadPage({
|
||||||
updated_at: msg.updated_at || new Date().toISOString(),
|
updated_at: msg.updated_at || new Date().toISOString(),
|
||||||
agent_id: (msg as any).agent_id,
|
agent_id: (msg as any).agent_id,
|
||||||
agents: (msg as any).agents,
|
agents: (msg as any).agents,
|
||||||
|
user_feedback: msg.user_feedback ?? null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setMessages(unifiedMessages);
|
setMessages(unifiedMessages);
|
||||||
|
|
|
@ -904,7 +904,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|
|
@ -10,13 +10,14 @@ type SubmitStatus = 'idle' | 'submitting' | 'success' | 'error';
|
||||||
|
|
||||||
interface FeedbackProps {
|
interface FeedbackProps {
|
||||||
messageId: string;
|
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 [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 [feedback, setFeedback] = useState<string>('');
|
||||||
const [submitStatus, setSubmitStatus] = useState<SubmitStatus>('idle');
|
const [submitStatus, setSubmitStatus] = useState<SubmitStatus>(initialFeedback !== null ? 'success' : 'idle');
|
||||||
|
|
||||||
const handleClick = (isGood: boolean) => {
|
const handleClick = (isGood: boolean) => {
|
||||||
setResponseIsGood(isGood);
|
setResponseIsGood(isGood);
|
||||||
|
|
|
@ -25,6 +25,7 @@ export interface UnifiedMessage {
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
avatar_color?: string;
|
avatar_color?: string;
|
||||||
}; // Agent information from join
|
}; // 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
|
// Helper type for parsed content - structure depends on message.type
|
||||||
|
|
|
@ -29,6 +29,7 @@ interface ApiMessageType {
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
avatar_color?: string;
|
avatar_color?: string;
|
||||||
};
|
};
|
||||||
|
user_feedback?: boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define the structure returned by the hook
|
// Define the structure returned by the hook
|
||||||
|
@ -68,6 +69,7 @@ const mapApiMessagesToUnified = (
|
||||||
metadata: msg.metadata || '{}',
|
metadata: msg.metadata || '{}',
|
||||||
created_at: msg.created_at || new Date().toISOString(),
|
created_at: msg.created_at || new Date().toISOString(),
|
||||||
updated_at: msg.updated_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,
|
agent_id: (msg as any).agent_id,
|
||||||
agents: (msg as any).agents,
|
agents: (msg as any).agents,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -76,6 +76,11 @@ export type Message = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface MessageWithFeedback extends Message {
|
||||||
|
message_id: string;
|
||||||
|
user_feedback?: boolean | null;
|
||||||
|
}
|
||||||
|
|
||||||
export type AgentRun = {
|
export type AgentRun = {
|
||||||
id: string;
|
id: string;
|
||||||
thread_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();
|
const supabase = createClient();
|
||||||
|
|
||||||
let allMessages: Message[] = [];
|
let allMessages: MessageWithFeedback[] = [];
|
||||||
let from = 0;
|
let from = 0;
|
||||||
const batchSize = 1000;
|
const batchSize = 1000;
|
||||||
let hasMore = true;
|
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);
|
console.log('[API] Messages fetched count:', allMessages.length);
|
||||||
|
|
||||||
return allMessages;
|
return allMessages;
|
||||||
|
|
Loading…
Reference in New Issue