diff --git a/bots/models/convo_msg.py b/bots/models/convo_msg.py index 6a1a52e96..aaf189235 100644 --- a/bots/models/convo_msg.py +++ b/bots/models/convo_msg.py @@ -9,7 +9,7 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.db import models -from django.db.models import OuterRef, QuerySet, Subquery +from django.db.models import OuterRef, Q, QuerySet, Subquery from django.utils.text import Truncator from bots.custom_fields import CustomURLField @@ -597,6 +597,65 @@ def __str__(self): return self.url +def db_msgs_to_api_json(msgs: list["Message"]) -> typing.Iterator[dict]: + from daras_ai_v2.bots import parse_bot_html + from routers.bots_api import MSG_ID_PREFIX + + for msg in msgs: + msg: Message + images = list( + msg.attachments.filter( + metadata__mime_type__startswith="image/" + ).values_list("url", flat=True) + ) + audios = list( + msg.attachments.filter( + metadata__mime_type__startswith="audio/" + ).values_list("url", flat=True) + ) + audio = audios and audios[0] + if msg.role == CHATML_ROLE_USER: + # any document type other than audio/image + documents = list( + msg.attachments.exclude( + Q(metadata__mime_type__startswith="image/") + | Q(metadata__mime_type__startswith="audio/") + ).values_list("url", flat=True) + ) + yield { + "role": msg.role, + "input_prompt": msg.display_content or msg.content, + "input_images": images, + "input_audio": audio, + "input_documents": documents, + "created_at": msg.created_at.isoformat(), + } + elif msg.role == CHATML_ROLE_ASSISTANT: + saved_run = msg.saved_run + references = [] + web_url = "" + if saved_run: + references = saved_run.state.get("references") or [] + web_url = saved_run.get_app_url() + buttons, text, _ = parse_bot_html(msg.display_content) + yield { + "role": msg.role, + "created_at": msg.created_at.isoformat(), + "status": "completed", + "type": "final_response", + "raw_output_text": [msg.content], + "output_text": [text], + "buttons": buttons, + "output_images": images, + "output_audio": audio, + "web_url": web_url, + "user_message_id": msg.platform_msg_id, + "bot_message_id": msg.platform_msg_id + and msg.platform_msg_id.removeprefix(MSG_ID_PREFIX), + "references": references, + } + + class FeedbackQuerySet(models.QuerySet): def to_df( self, tz=pytz.timezone(settings.TIME_ZONE), row_limit=10000 diff --git a/daras_ai_v2/bot_integration_widgets.py b/daras_ai_v2/bot_integration_widgets.py index 776eff93a..b47273390 100644 --- a/daras_ai_v2/bot_integration_widgets.py +++ b/daras_ai_v2/bot_integration_widgets.py @@ -451,6 +451,7 @@ def web_widget_config(bi: BotIntegration, user: AppUser | None, hostname: str | enableAudioMessage=True, enableConversations=True, enableSourcePreview=True, + enableShareConversation=False, branding=( dict(showPoweredByGooey=True) | bi.web_config_extras.get("branding", {}) @@ -470,6 +471,9 @@ def web_widget_config(bi: BotIntegration, user: AppUser | None, hostname: str | config["enableConversations"] = gui.checkbox( 'Show "New Chat"', value=config["enableConversations"] ) + config["enableShareConversation"] = gui.checkbox( + "Enable Conversation Sharing", value=config["enableShareConversation"] + ) with scol2: config["enableAudioMessage"] = gui.checkbox( "Enable Audio Message", value=config["enableAudioMessage"] diff --git a/routers/root.py b/routers/root.py index f3829180a..c204dbb89 100644 --- a/routers/root.py +++ b/routers/root.py @@ -25,6 +25,10 @@ from app_users.models import AppUser from bots.models import BotIntegration, PublishedRun, Workflow +from bots.models.convo_msg import ( + Conversation, + db_msgs_to_api_json, +) from daras_ai.image_input import safe_filename, upload_file_from_bytes from daras_ai_v2 import icons, settings from daras_ai_v2.api_examples_widget import api_example_generator @@ -537,8 +541,12 @@ def chat_explore_route(request: Request): @app.get("/chat/{integration_name}-{integration_id}/") +@app.get("/chat/{integration_name}-{integration_id}/share/{conversation_id}/") def chat_route( - request: Request, integration_id: str = None, integration_name: str = None + request: Request, + integration_id: str | None = None, + integration_name: str | None = None, + conversation_id: str | None = None, ): from daras_ai_v2.bot_integration_widgets import get_web_widget_embed_code from routers.bots_api import api_hashids @@ -548,12 +556,36 @@ def chat_route( except (IndexError, BotIntegration.DoesNotExist): raise HTTPException(status_code=404) + if conversation_id: + try: + conversation: Conversation = Conversation.objects.get( + id=api_hashids.decode(conversation_id)[0], + ) + except (IndexError, Conversation.DoesNotExist): + raise HTTPException(status_code=404) + messages = list(db_msgs_to_api_json(conversation.last_n_msgs())) + conversation_data = dict( + id=conversation_id, + bot_id=integration_id, + timestamp=conversation.created_at.isoformat(), + user_id=conversation.web_user_id, + messages=messages, + ) + else: + conversation_data = None + return templates.TemplateResponse( "chat_fullscreen.html", { "request": request, "bi": bi, - "embed_code": get_web_widget_embed_code(bi, config=dict(mode="fullscreen")), + "embed_code": get_web_widget_embed_code( + bi, + config=dict( + mode="fullscreen", + conversationData=conversation_data, + ), + ), "meta": raw_build_meta_tags( url=get_og_url_path(request), title=f"Chat with {bi.name}",