diff --git a/backend/danswer/configs/constants.py b/backend/danswer/configs/constants.py index 9e0d318c5ec8..9c707d43a057 100644 --- a/backend/danswer/configs/constants.py +++ b/backend/danswer/configs/constants.py @@ -119,6 +119,11 @@ class AuthType(str, Enum): SAML = "saml" +class QAFeedbackType(str, Enum): + LIKE = "like" # User likes the answer, used for metrics + DISLIKE = "dislike" # User dislikes the answer, used for metrics + + class SearchFeedbackType(str, Enum): ENDORSE = "endorse" # boost this document for all future queries REJECT = "reject" # down-boost this document for all future queries diff --git a/backend/ee/danswer/db/analytics.py b/backend/ee/danswer/db/analytics.py index 1eba26a8585e..a0b623646d2f 100644 --- a/backend/ee/danswer/db/analytics.py +++ b/backend/ee/danswer/db/analytics.py @@ -9,8 +9,10 @@ from sqlalchemy import func from sqlalchemy import select from sqlalchemy.orm import Session -from danswer.configs.constants import QAFeedbackType -from danswer.db.models import QueryEvent +from danswer.configs.constants import MessageType +from danswer.db.models import ChatMessage +from danswer.db.models import ChatMessageFeedback +from danswer.db.models import ChatSession def fetch_query_analytics( @@ -20,19 +22,29 @@ def fetch_query_analytics( ) -> Sequence[tuple[int, int, int, datetime.date]]: stmt = ( select( - func.count(QueryEvent.id), - func.sum(case((QueryEvent.feedback == QAFeedbackType.LIKE, 1), else_=0)), - func.sum(case((QueryEvent.feedback == QAFeedbackType.DISLIKE, 1), else_=0)), - cast(QueryEvent.time_created, Date), + func.count(ChatMessage.id), + func.sum(case((ChatMessageFeedback.is_positive, 1), else_=0)), + func.sum( + case( + (ChatMessageFeedback.is_positive == False, 1), else_=0 # noqa: E712 + ) + ), + cast(ChatMessage.time_sent, Date), + ) + .join( + ChatMessageFeedback, + ChatMessageFeedback.chat_message_id == ChatMessage.id, + isouter=True, ) .where( - QueryEvent.time_created >= start, + ChatMessage.time_sent >= start, ) .where( - QueryEvent.time_created <= end, + ChatMessage.time_sent <= end, ) - .group_by(cast(QueryEvent.time_created, Date)) - .order_by(cast(QueryEvent.time_created, Date)) + .where(ChatMessage.message_type == MessageType.ASSISTANT) + .group_by(cast(ChatMessage.time_sent, Date)) + .order_by(cast(ChatMessage.time_sent, Date)) ) return db_session.execute(stmt).all() # type: ignore @@ -45,20 +57,26 @@ def fetch_per_user_query_analytics( ) -> Sequence[tuple[int, int, int, datetime.date, UUID]]: stmt = ( select( - func.count(QueryEvent.id), - func.sum(case((QueryEvent.feedback == QAFeedbackType.LIKE, 1), else_=0)), - func.sum(case((QueryEvent.feedback == QAFeedbackType.DISLIKE, 1), else_=0)), - cast(QueryEvent.time_created, Date), - QueryEvent.user_id, + func.count(ChatMessage.id), + func.sum(case((ChatMessageFeedback.is_positive, 1), else_=0)), + func.sum( + case( + (ChatMessageFeedback.is_positive == False, 1), else_=0 # noqa: E712 + ) + ), + cast(ChatMessage.time_sent, Date), + ChatSession.user_id, + ) + .join(ChatSession, ChatSession.id == ChatMessage.chat_session_id) + .where( + ChatMessage.time_sent >= start, ) .where( - QueryEvent.time_created >= start, + ChatMessage.time_sent <= end, ) - .where( - QueryEvent.time_created <= end, - ) - .group_by(cast(QueryEvent.time_created, Date), QueryEvent.user_id) - .order_by(cast(QueryEvent.time_created, Date), QueryEvent.user_id) + .where(ChatMessage.message_type == MessageType.ASSISTANT) + .group_by(cast(ChatMessage.time_sent, Date), ChatSession.user_id) + .order_by(cast(ChatMessage.time_sent, Date), ChatSession.user_id) ) return db_session.execute(stmt).all() # type: ignore diff --git a/backend/ee/danswer/db/query_history.py b/backend/ee/danswer/db/query_history.py index 6a74290063da..2ddaaac8249a 100644 --- a/backend/ee/danswer/db/query_history.py +++ b/backend/ee/danswer/db/query_history.py @@ -10,34 +10,39 @@ from sqlalchemy.orm import aliased from sqlalchemy.orm import Session from sqlalchemy.orm.attributes import InstrumentedAttribute -from danswer.configs.constants import QAFeedbackType -from danswer.db.models import QueryEvent +from danswer.configs.constants import MessageType +from danswer.db.models import ChatMessage +from danswer.db.models import ChatSession from danswer.db.models import User -SortByOptions = Literal["time_created", "feedback"] +SortByOptions = Literal["time_sent"] def build_query_history_query( start: datetime.datetime, end: datetime.datetime, - query: str | None, - feedback_type: QAFeedbackType | None, sort_by_field: SortByOptions, sort_by_direction: Literal["asc", "desc"], offset: int, limit: int | None, -) -> Select[tuple[QueryEvent]]: +) -> Select[tuple[ChatMessage]]: stmt = ( - select(QueryEvent) + select(ChatMessage) .where( - QueryEvent.time_created >= start, + ChatMessage.time_sent >= start, ) .where( - QueryEvent.time_created <= end, + ChatMessage.time_sent <= end, + ) + .where( + or_( + ChatMessage.message_type == MessageType.ASSISTANT, + ChatMessage.message_type == MessageType.USER, + ), ) ) - order_by_field = cast(InstrumentedAttribute, getattr(QueryEvent, sort_by_field)) + order_by_field = cast(InstrumentedAttribute, getattr(ChatMessage, sort_by_field)) if sort_by_direction == "asc": stmt = stmt.order_by(order_by_field.asc()) else: @@ -48,17 +53,6 @@ def build_query_history_query( if limit: stmt = stmt.limit(limit) - if query: - stmt = stmt.where( - or_( - QueryEvent.llm_answer.ilike(f"%{query}%"), - QueryEvent.query.ilike(f"%{query}%"), - ) - ) - - if feedback_type: - stmt = stmt.where(QueryEvent.feedback == feedback_type) - return stmt @@ -66,18 +60,14 @@ def fetch_query_history( db_session: Session, start: datetime.datetime, end: datetime.datetime, - query: str | None = None, - feedback_type: QAFeedbackType | None = None, - sort_by_field: SortByOptions = "time_created", + sort_by_field: SortByOptions = "time_sent", sort_by_direction: Literal["asc", "desc"] = "desc", offset: int = 0, limit: int | None = 500, -) -> Sequence[QueryEvent]: +) -> Sequence[ChatMessage]: stmt = build_query_history_query( start=start, end=end, - query=query, - feedback_type=feedback_type, sort_by_field=sort_by_field, sort_by_direction=sort_by_direction, offset=offset, @@ -91,27 +81,25 @@ def fetch_query_history_with_user_email( db_session: Session, start: datetime.datetime, end: datetime.datetime, - query: str | None = None, - feedback_type: QAFeedbackType | None = None, - sort_by_field: SortByOptions = "time_created", + sort_by_field: SortByOptions = "time_sent", sort_by_direction: Literal["asc", "desc"] = "desc", offset: int = 0, limit: int | None = 500, -) -> Sequence[tuple[QueryEvent, str | None]]: +) -> Sequence[tuple[ChatMessage, str | None]]: subquery = build_query_history_query( start=start, end=end, - query=query, - feedback_type=feedback_type, sort_by_field=sort_by_field, sort_by_direction=sort_by_direction, offset=offset, limit=limit, ).subquery() - subquery_alias = aliased(QueryEvent, subquery) + subquery_alias = aliased(ChatMessage, subquery) - stmt_with_user_email = select(subquery_alias, User.email).join( # type: ignore - User, subquery_alias.user_id == User.id, isouter=True + stmt_with_user_email = ( + select(subquery_alias, User.email) # type: ignore + .join(ChatSession, subquery_alias.chat_session_id == ChatSession.id) + .join(User, ChatSession.user_id == User.id, isouter=True) ) return db_session.execute(stmt_with_user_email).all() # type: ignore diff --git a/backend/ee/danswer/server/query_history/api.py b/backend/ee/danswer/server/query_history/api.py index 387e8dfdb282..1931e3315c3c 100644 --- a/backend/ee/danswer/server/query_history/api.py +++ b/backend/ee/danswer/server/query_history/api.py @@ -1,6 +1,6 @@ import csv import io -from collections.abc import Iterable +from collections import defaultdict from datetime import datetime from datetime import timedelta from datetime import timezone @@ -14,11 +14,11 @@ from sqlalchemy.orm import Session import danswer.db.models as db_models from danswer.auth.users import current_admin_user +from danswer.configs.constants import MessageType from danswer.configs.constants import QAFeedbackType +from danswer.db.chat import get_chat_session_by_id from danswer.db.engine import get_session -from danswer.db.feedback import fetch_query_event_by_id -from danswer.db.models import Document -from ee.danswer.db.document import fetch_documents_from_ids +from danswer.db.models import ChatMessage from ee.danswer.db.query_history import ( fetch_query_history_with_user_email, ) @@ -35,45 +35,112 @@ class AbridgedSearchDoc(BaseModel): link: str | None -class QuerySnapshot(BaseModel): +class MessageSnapshot(BaseModel): + message: str + message_type: MessageType + documents: list[AbridgedSearchDoc] + feedback: QAFeedbackType | None + time_created: datetime + + @classmethod + def build(cls, message: ChatMessage) -> "MessageSnapshot": + latest_messages_feedback_obj = ( + message.chat_message_feedbacks[-1] + if len(message.chat_message_feedbacks) > 0 + else None + ) + message_feedback = ( + ( + QAFeedbackType.LIKE + if latest_messages_feedback_obj.is_positive + else QAFeedbackType.DISLIKE + ) + if latest_messages_feedback_obj + else None + ) + + return cls( + message=message.message, + message_type=message.message_type, + documents=[ + AbridgedSearchDoc( + document_id=document.document_id, + semantic_identifier=document.semantic_id, + link=document.link, + ) + for document in message.search_docs + ], + feedback=message_feedback, + time_created=message.time_sent, + ) + + +class ChatSessionSnapshot(BaseModel): id: int user_email: str | None - query: str - llm_answer: str | None - retrieved_documents: list[AbridgedSearchDoc] - feedback: QAFeedbackType | None + name: str | None + messages: list[MessageSnapshot] time_created: datetime @classmethod def build( cls, - query_event: db_models.QueryEvent, - user_email: str | None, - documents: Iterable[Document], - ) -> "QuerySnapshot": + messages: list[ChatMessage], + ) -> "ChatSessionSnapshot": + if len(messages) == 0: + raise ValueError("No messages provided") + + chat_session = messages[0].chat_session + return cls( - id=query_event.id, - user_email=user_email, - query=query_event.query, - llm_answer=query_event.llm_answer, - retrieved_documents=[ - AbridgedSearchDoc( - document_id=document.id, - semantic_identifier=document.semantic_id, - link=document.link, - ) - for document in documents + id=chat_session.id, + user_email=chat_session.user.email if chat_session.user else None, + name=chat_session.description, + messages=[ + MessageSnapshot.build(message) + for message in sorted(messages, key=lambda m: m.time_sent) + if message.message_type != MessageType.SYSTEM ], - feedback=query_event.feedback, - time_created=query_event.time_created, + time_created=chat_session.time_created, ) + +class QuestionAnswerPairSnapshot(BaseModel): + user_message: str + ai_response: str + retrieved_documents: list[AbridgedSearchDoc] + feedback: QAFeedbackType | None + time_created: datetime + + @classmethod + def from_chat_session_snapshot( + cls, + chat_session_snapshot: ChatSessionSnapshot, + ) -> list["QuestionAnswerPairSnapshot"]: + message_pairs: list[tuple[MessageSnapshot, MessageSnapshot]] = [] + for ind in range(1, len(chat_session_snapshot.messages), 2): + message_pairs.append( + ( + chat_session_snapshot.messages[ind - 1], + chat_session_snapshot.messages[ind], + ) + ) + + return [ + cls( + user_message=user_message.message, + ai_response=ai_message.message, + retrieved_documents=ai_message.documents, + feedback=ai_message.feedback, + time_created=user_message.time_created, + ) + for user_message, ai_message in message_pairs + ] + def to_json(self) -> dict[str, str]: return { - "id": str(self.id), - "query": self.query, - "user_email": self.user_email or "", - "llm_answer": self.llm_answer or "", + "user_message": self.user_message, + "ai_response": self.ai_response, "retrieved_documents": "|".join( [ doc.link or doc.semantic_identifier @@ -85,61 +152,52 @@ class QuerySnapshot(BaseModel): } -def fetch_and_process_query_history( +def fetch_and_process_chat_session_history( db_session: Session, start: datetime | None, end: datetime | None, feedback_type: QAFeedbackType | None, limit: int | None = 500, -) -> list[QuerySnapshot]: - query_history_with_user_email = fetch_query_history_with_user_email( +) -> list[ChatSessionSnapshot]: + chat_messages_with_user_email = fetch_query_history_with_user_email( db_session=db_session, start=start or ( datetime.now(tz=timezone.utc) - timedelta(days=30) ), # default is 30d lookback end=end or datetime.now(tz=timezone.utc), - feedback_type=feedback_type, limit=limit, ) + session_id_to_messages: dict[int, list[ChatMessage]] = defaultdict(list) + for message, _ in chat_messages_with_user_email: + session_id_to_messages[message.chat_session_id].append(message) - all_relevant_document_ids: set[str] = set() - for query_event, _ in query_history_with_user_email: - all_relevant_document_ids = all_relevant_document_ids.union( - query_event.retrieved_document_ids or [] - ) - document_id_to_document = { - document.id: document - for document in fetch_documents_from_ids( - db_session, list(all_relevant_document_ids) - ) - } - - query_snapshots: list[QuerySnapshot] = [] - for query_event, user_email in query_history_with_user_email: - unique_document_ids = set(query_event.retrieved_document_ids or []) - documents = [ - document_id_to_document[doc_id] - for doc_id in unique_document_ids - if doc_id in document_id_to_document - ] - query_snapshots.append( - QuerySnapshot.build( - query_event=query_event, user_email=user_email, documents=documents + chat_session_snapshots = [ + ChatSessionSnapshot.build(messages) + for messages in session_id_to_messages.values() + ] + if feedback_type: + chat_session_snapshots = [ + chat_session_snapshot + for chat_session_snapshot in chat_session_snapshots + if any( + message.feedback == feedback_type + for message in chat_session_snapshot.messages ) - ) - return query_snapshots + ] + + return chat_session_snapshots -@router.get("/admin/query-history") -def get_query_history( +@router.get("/admin/chat-session-history") +def get_chat_session_history( feedback_type: QAFeedbackType | None = None, start: datetime | None = None, end: datetime | None = None, _: db_models.User | None = Depends(current_admin_user), db_session: Session = Depends(get_session), -) -> list[QuerySnapshot]: - return fetch_and_process_query_history( +) -> list[ChatSessionSnapshot]: + return fetch_and_process_chat_session_history( db_session=db_session, start=start, end=end, @@ -147,24 +205,22 @@ def get_query_history( ) -@router.get("/admin/query-history/{query_id}") -def get_query( - query_id: int, +@router.get("/admin/chat-session-history/{chat_session_id}") +def get_chat_session( + chat_session_id: int, _: db_models.User | None = Depends(current_admin_user), db_session: Session = Depends(get_session), -) -> QuerySnapshot: +) -> ChatSessionSnapshot: try: - query_event = fetch_query_event_by_id(query_id=query_id, db_session=db_session) + chat_session = get_chat_session_by_id( + chat_session_id=chat_session_id, user_id=None, db_session=db_session + ) except ValueError: - raise HTTPException(400, f"Query event with id '{query_id}' does not exist.") - documents = fetch_documents_from_ids( - db_session, query_event.retrieved_document_ids or [] - ) - return QuerySnapshot.build( - query_event=query_event, - user_email=query_event.user.email if query_event.user else None, - documents=documents, - ) + raise HTTPException( + 400, f"Chat session with id '{chat_session_id}' does not exist." + ) + + return ChatSessionSnapshot.build(messages=chat_session.messages) @router.get("/admin/query-history-csv") @@ -172,7 +228,7 @@ def get_query_history_as_csv( _: db_models.User | None = Depends(current_admin_user), db_session: Session = Depends(get_session), ) -> StreamingResponse: - complete_query_history = fetch_and_process_query_history( + complete_chat_session_history = fetch_and_process_chat_session_history( db_session=db_session, start=datetime.fromtimestamp(0, tz=timezone.utc), end=datetime.now(tz=timezone.utc), @@ -180,11 +236,19 @@ def get_query_history_as_csv( limit=None, ) + question_answer_pairs: list[QuestionAnswerPairSnapshot] = [] + for chat_session_snapshot in complete_chat_session_history: + question_answer_pairs.extend( + QuestionAnswerPairSnapshot.from_chat_session_snapshot(chat_session_snapshot) + ) + # Create an in-memory text stream stream = io.StringIO() - writer = csv.DictWriter(stream, fieldnames=list(QuerySnapshot.__fields__.keys())) + writer = csv.DictWriter( + stream, fieldnames=list(QuestionAnswerPairSnapshot.__fields__.keys()) + ) writer.writeheader() - for row in complete_query_history: + for row in question_answer_pairs: writer.writerow(row.to_json()) # Reset the stream's position to the start diff --git a/web/src/app/ee/admin/groups/UserEditor.tsx b/web/src/app/ee/admin/groups/UserEditor.tsx index f14472a29e5c..12112b7b27bf 100644 --- a/web/src/app/ee/admin/groups/UserEditor.tsx +++ b/web/src/app/ee/admin/groups/UserEditor.tsx @@ -67,7 +67,9 @@ export const UserEditor = ({ })} onSelect={(option) => { setSelectedUserIds([ - ...Array.from(new Set([...selectedUserIds, option.value as string])), + ...Array.from( + new Set([...selectedUserIds, option.value as string]) + ), ]); }} itemComponent={({ option }) => ( diff --git a/web/src/app/ee/admin/groups/[groupId]/AddConnectorForm.tsx b/web/src/app/ee/admin/groups/[groupId]/AddConnectorForm.tsx index 341cf6ade60b..a74a4abdd850 100644 --- a/web/src/app/ee/admin/groups/[groupId]/AddConnectorForm.tsx +++ b/web/src/app/ee/admin/groups/[groupId]/AddConnectorForm.tsx @@ -91,7 +91,10 @@ export const AddConnectorForm: React.FC = ({ onSelect={(option) => { setSelectedCCPairIds([ ...Array.from( - new Set([...selectedCCPairIds, parseInt(option.value as string)]) + new Set([ + ...selectedCCPairIds, + parseInt(option.value as string), + ]) ), ]); }} diff --git a/web/src/app/ee/admin/performance/DateRangeSelector.tsx b/web/src/app/ee/admin/performance/DateRangeSelector.tsx index 63164a5bcd47..7f5972e3cd7b 100644 --- a/web/src/app/ee/admin/performance/DateRangeSelector.tsx +++ b/web/src/app/ee/admin/performance/DateRangeSelector.tsx @@ -2,6 +2,7 @@ import { DateRangePicker, DateRangePickerItem, DateRangePickerValue, + Text, } from "@tremor/react"; import { getXDaysAgo } from "./dateUtils"; @@ -16,9 +17,7 @@ export function DateRangeSelector({ }) { return (
-
- Date Range -
+ Date Range +
{/* TODO: remove this `dark` once we have a mode selector */} -
- -

Analytics

-
+ } /> diff --git a/web/src/app/ee/admin/performance/analytics/types.ts b/web/src/app/ee/admin/performance/analytics/types.ts index e656851a005c..3ef2d2497c31 100644 --- a/web/src/app/ee/admin/performance/analytics/types.ts +++ b/web/src/app/ee/admin/performance/analytics/types.ts @@ -18,12 +18,18 @@ export interface AbridgedSearchDoc { link: string | null; } -export interface QuerySnapshot { - id: number; - query: string; - user_email: string | null; - llm_answer: string; - retrieved_documents: AbridgedSearchDoc[]; - time_created: string; +export interface MessageSnapshot { + message: string; + message_type: "user" | "assistant"; + documents: AbridgedSearchDoc[]; feedback: Feedback | null; + time_created: string; +} + +export interface ChatSessionSnapshot { + id: number; + user_email: string | null; + name: string | null; + messages: MessageSnapshot[]; + time_created: string; } diff --git a/web/src/app/ee/admin/performance/lib.ts b/web/src/app/ee/admin/performance/lib.ts index ee5a9923e73d..76ac0ba132b3 100644 --- a/web/src/app/ee/admin/performance/lib.ts +++ b/web/src/app/ee/admin/performance/lib.ts @@ -1,8 +1,8 @@ import { errorHandlingFetcher } from "@/lib/fetcher"; import useSWR, { mutate } from "swr"; import { + ChatSessionSnapshot, QueryAnalytics, - QuerySnapshot, UserAnalytics, } from "./analytics/types"; import { useState } from "react"; @@ -55,12 +55,12 @@ export const useQueryHistory = () => { useState(null); const [timeRange, setTimeRange] = useTimeRange(); - const url = buildApiPath("/api/admin/query-history", { + const url = buildApiPath("/api/admin/chat-session-history", { feedback_type: selectedFeedbackType, start: convertDateToStartOfDay(timeRange.from)?.toISOString(), end: convertDateToEndOfDay(timeRange.to)?.toISOString(), }); - const swrResponse = useSWR(url, errorHandlingFetcher); + const swrResponse = useSWR(url, errorHandlingFetcher); return { ...swrResponse, diff --git a/web/src/app/ee/admin/performance/query-history/DownloadAsCSV.tsx b/web/src/app/ee/admin/performance/query-history/DownloadAsCSV.tsx index 550d9d3ceebe..1d5217da1e0c 100644 --- a/web/src/app/ee/admin/performance/query-history/DownloadAsCSV.tsx +++ b/web/src/app/ee/admin/performance/query-history/DownloadAsCSV.tsx @@ -4,7 +4,7 @@ export function DownloadAsCSV() { return ( Download as CSV diff --git a/web/src/app/ee/admin/performance/query-history/FeedbackBadge.tsx b/web/src/app/ee/admin/performance/query-history/FeedbackBadge.tsx index 5000f301dc85..ebe75e7a02b8 100644 --- a/web/src/app/ee/admin/performance/query-history/FeedbackBadge.tsx +++ b/web/src/app/ee/admin/performance/query-history/FeedbackBadge.tsx @@ -1,7 +1,11 @@ import { Feedback } from "@/lib/types"; import { Badge } from "@tremor/react"; -export function FeedbackBadge({ feedback }: { feedback?: Feedback | null }) { +export function FeedbackBadge({ + feedback, +}: { + feedback?: Feedback | "mixed" | null; +}) { let feedbackBadge; switch (feedback) { case "like": @@ -18,6 +22,13 @@ export function FeedbackBadge({ feedback }: { feedback?: Feedback | null }) { ); break; + case "mixed": + feedbackBadge = ( + + Mixed + + ); + break; default: feedbackBadge = ( diff --git a/web/src/app/ee/admin/performance/query-history/QueryHistoryTable.tsx b/web/src/app/ee/admin/performance/query-history/QueryHistoryTable.tsx index a0f18679ef1d..fd3348747b72 100644 --- a/web/src/app/ee/admin/performance/query-history/QueryHistoryTable.tsx +++ b/web/src/app/ee/admin/performance/query-history/QueryHistoryTable.tsx @@ -13,12 +13,9 @@ import { import { Divider } from "@tremor/react"; import { Select, SelectItem } from "@tremor/react"; import { ThreeDotsLoader } from "@/components/Loading"; -import { QuerySnapshot } from "../analytics/types"; -import { - timestampToDateString, - timestampToReadableDate, -} from "@/lib/dateUtils"; -import { FiBook, FiFrown, FiMinus, FiSmile } from "react-icons/fi"; +import { ChatSessionSnapshot } from "../analytics/types"; +import { timestampToReadableDate } from "@/lib/dateUtils"; +import { FiFrown, FiMinus, FiSmile } from "react-icons/fi"; import { useState } from "react"; import { Feedback } from "@/lib/types"; import { DateRangeSelector } from "../DateRangeSelector"; @@ -30,42 +27,47 @@ import { DownloadAsCSV } from "./DownloadAsCSV"; const NUM_IN_PAGE = 20; function QueryHistoryTableRow({ - querySnapshot, + chatSessionSnapshot, }: { - querySnapshot: QuerySnapshot; + chatSessionSnapshot: ChatSessionSnapshot; }) { + let finalFeedback: Feedback | "mixed" | null = null; + for (const message of chatSessionSnapshot.messages) { + if (message.feedback) { + if (finalFeedback === null) { + finalFeedback = message.feedback; + } else if (finalFeedback !== message.feedback) { + finalFeedback = "mixed"; + } + } + } + return ( - {querySnapshot.query} - {querySnapshot.llm_answer} + {chatSessionSnapshot.messages[0]?.message || "-"} - {querySnapshot.retrieved_documents.slice(0, 5).map((document) => ( -
- {" "} -

- {document.semantic_identifier} -

-
- ))} + + {chatSessionSnapshot.messages[1]?.message || "-"} +
- + - {querySnapshot.user_email || "-"} + {chatSessionSnapshot.user_email || "-"} - {timestampToReadableDate(querySnapshot.time_created)} + {timestampToReadableDate(chatSessionSnapshot.time_created)} {/* Wrapping in to avoid console warnings */} @@ -82,9 +84,7 @@ function SelectFeedbackType({ }) { return (
-
- Feedback Type -
+ Feedback Type