11import json
22from urllib .request import Request
3+ from uuid import uuid4
34
45from django .http import StreamingHttpResponse
56
67from baserow_premium .license .handler import LicenseHandler
78from drf_spectacular .openapi import OpenApiParameter , OpenApiTypes
89from drf_spectacular .utils import OpenApiResponse , extend_schema
10+ from loguru import logger
911from rest_framework .response import Response
12+ from rest_framework .status import HTTP_204_NO_CONTENT
1013from rest_framework .views import APIView
1114
1215from baserow .api .decorators import (
1821from baserow .api .pagination import LimitOffsetPagination
1922from baserow .api .schemas import get_error_schema
2023from baserow .api .serializers import get_example_pagination_serializer_class
24+ from baserow .api .sessions import set_client_undo_redo_action_group_id
2125from baserow .core .exceptions import UserNotInWorkspace , WorkspaceDoesNotExist
2226from baserow .core .feature_flags import FF_ASSISTANT , feature_flag_is_enabled
2327from baserow .core .handler import CoreHandler
24- from baserow_enterprise .api .assistant .errors import (
25- ERROR_ASSISTANT_CHAT_DOES_NOT_EXIST ,
26- ERROR_ASSISTANT_MODEL_NOT_SUPPORTED ,
27- )
2828from baserow_enterprise .assistant .exceptions import (
2929 AssistantChatDoesNotExist ,
30+ AssistantChatMessagePredictionDoesNotExist ,
3031 AssistantModelNotSupportedError ,
3132)
3233from baserow_enterprise .assistant .handler import AssistantHandler
34+ from baserow_enterprise .assistant .models import AssistantChatPrediction
3335from baserow_enterprise .assistant .operations import ChatAssistantChatOperationType
3436from baserow_enterprise .assistant .types import (
37+ AiErrorMessage ,
3538 AssistantMessageUnion ,
3639 HumanMessage ,
3740 UIContext ,
3841)
3942from baserow_enterprise .features import ASSISTANT
4043
44+ from .errors import (
45+ ERROR_ASSISTANT_CHAT_DOES_NOT_EXIST ,
46+ ERROR_ASSISTANT_MODEL_NOT_SUPPORTED ,
47+ ERROR_CANNOT_SUBMIT_MESSAGE_FEEDBACK ,
48+ )
4149from .serializers import (
4250 AssistantChatMessagesSerializer ,
4351 AssistantChatSerializer ,
4452 AssistantChatsRequestSerializer ,
4553 AssistantMessageRequestSerializer ,
4654 AssistantMessageSerializer ,
55+ AssistantRateChatMessageSerializer ,
4756)
4857
4958
@@ -139,7 +148,6 @@ class AssistantChatView(APIView):
139148 {
140149 UserNotInWorkspace : ERROR_USER_NOT_IN_GROUP ,
141150 WorkspaceDoesNotExist : ERROR_GROUP_DOES_NOT_EXIST ,
142- AssistantChatDoesNotExist : ERROR_ASSISTANT_CHAT_DOES_NOT_EXIST ,
143151 AssistantModelNotSupportedError : ERROR_ASSISTANT_MODEL_NOT_SUPPORTED ,
144152 }
145153 )
@@ -164,16 +172,33 @@ def post(self, request: Request, chat_uuid: str, data) -> StreamingHttpResponse:
164172
165173 # Clearing the user websocket_id will make sure real-time updates are sent
166174 chat .user .web_socket_id = None
167- # FIXME: As long as we don't allow users to change it, temporarily set the
168- # timezone to the one provided in the UI context
175+
176+ # Used to group all the actions done to produce this message together
177+ # so they can be undone in one go.
178+ set_client_undo_redo_action_group_id (chat .user , str (uuid4 ()))
179+
180+ # As long as we don't allow users to change it, temporarily set the timezone to
181+ # the one provided in the UI context so tools can use it if needed.
169182 chat .user .profile .timezone = ui_context .timezone
170183
171184 assistant = handler .get_assistant (chat )
185+ assistant .check_llm_ready_or_raise ()
172186 human_message = HumanMessage (content = data ["content" ], ui_context = ui_context )
173187
174188 async def stream_assistant_messages ():
175- async for msg in assistant .astream_messages (human_message ):
176- yield self ._stream_assistant_message (msg )
189+ try :
190+ async for msg in assistant .astream_messages (human_message ):
191+ yield self ._stream_assistant_message (msg )
192+ except Exception :
193+ logger .exception ("Error while streaming assistant messages" )
194+ yield self ._stream_assistant_message (
195+ AiErrorMessage (
196+ content = (
197+ "Oops, something went wrong and I cannot continue the conversation. "
198+ "Please try again."
199+ )
200+ )
201+ )
177202
178203 response = StreamingHttpResponse (
179204 stream_assistant_messages (),
@@ -230,3 +255,51 @@ def get(self, request: Request, chat_uuid: str) -> Response:
230255 serializer = AssistantChatMessagesSerializer ({"messages" : messages })
231256
232257 return Response (serializer .data )
258+
259+
260+ class AssistantChatMessageFeedbackView (APIView ):
261+ @extend_schema (
262+ tags = ["AI Assistant" ],
263+ operation_id = "submit_assistant_message_feedback" ,
264+ description = (
265+ "Provide sentiment and feedback for the given AI assistant chat message.\n \n "
266+ "This is an **advanced/enterprise** feature."
267+ ),
268+ responses = {
269+ 200 : None ,
270+ 400 : get_error_schema (
271+ ["ERROR_USER_NOT_IN_GROUP" , "ERROR_CANNOT_SUBMIT_MESSAGE_FEEDBACK" ]
272+ ),
273+ },
274+ )
275+ @validate_body (AssistantRateChatMessageSerializer , return_validated = True )
276+ @map_exceptions (
277+ {
278+ UserNotInWorkspace : ERROR_USER_NOT_IN_GROUP ,
279+ WorkspaceDoesNotExist : ERROR_GROUP_DOES_NOT_EXIST ,
280+ AssistantChatDoesNotExist : ERROR_ASSISTANT_CHAT_DOES_NOT_EXIST ,
281+ AssistantChatMessagePredictionDoesNotExist : ERROR_CANNOT_SUBMIT_MESSAGE_FEEDBACK ,
282+ }
283+ )
284+ def put (self , request : Request , message_id : int , data ) -> Response :
285+ feature_flag_is_enabled (FF_ASSISTANT , raise_if_disabled = True )
286+
287+ handler = AssistantHandler ()
288+ message = handler .get_chat_message_by_id (request .user , message_id )
289+ LicenseHandler .raise_if_user_doesnt_have_feature (
290+ ASSISTANT , request .user , message .chat .workspace
291+ )
292+
293+ try :
294+ prediction : AssistantChatPrediction = message .prediction
295+ except AttributeError :
296+ raise AssistantChatMessagePredictionDoesNotExist (
297+ f"Message with ID { message_id } does not have an associated prediction."
298+ )
299+
300+ prediction .human_sentiment = data ["sentiment" ]
301+ prediction .human_feedback = data .get ("feedback" ) or ""
302+ prediction .save (
303+ update_fields = ["human_sentiment" , "human_feedback" , "updated_on" ]
304+ )
305+ return Response (status = HTTP_204_NO_CONTENT )
0 commit comments