From 7c003d89794588ea5025f3ee12d7f1948c3fa2ea Mon Sep 17 00:00:00 2001 From: Anish Date: Wed, 10 Dec 2025 04:09:39 +0530 Subject: [PATCH 01/11] feature: introduce /share route for web widget - make new method db_msgs_to_api_json - fetch attachments and append according to message role - send as conversationData in web widget config - redirect to base /chat url if no ceoonverstion found --- bots/models/convo_msg.py | 92 +++++++++++++++++++++++++++++++++++++++- routers/root.py | 35 ++++++++++++++- 2 files changed, 123 insertions(+), 4 deletions(-) diff --git a/bots/models/convo_msg.py b/bots/models/convo_msg.py index 6a1a52e96..121d97f16 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 @@ -474,7 +474,6 @@ def db_msgs_to_entries(msgs: list["Message"]) -> list["ConversationEntry"]: ) return entries - class Message(models.Model): conversation = models.ForeignKey( "Conversation", on_delete=models.CASCADE, related_name="messages" @@ -596,6 +595,95 @@ def __str__(self): return f"{self.metadata.name} ({self.url})" return self.url +def db_msgs_to_api_json(msgs: list["Message"]) -> list[dict]: + from daras_ai_v2.bots import parse_bot_html + from routers.bots_api import MSG_ID_PREFIX + + api_messages = [None] * len(msgs) + for i, msg in enumerate(msgs): + msg: Message + if msg.role == CHATML_ROLE_USER: + input_images = ( + list( + MessageAttachment.objects.filter( + message=msg, metadata__mime_type__startswith="image/" + ).values_list("url", flat=True) + ) + or [] + ) + + input_audio = ( + list( + MessageAttachment.objects.filter( + message=msg, metadata__mime_type__startswith="audio/" + ).values_list("url", flat=True) + ) + or "" + ) + + # any document type other than audio/image + input_documents = ( + list( + MessageAttachment.objects.filter(message=msg) + .exclude( + Q(metadata__mime_type__startswith="image/") + | Q(metadata__mime_type__startswith="audio/") + ) + .values_list("url", flat=True) + ) + or [] + ) + api_messages[i] = { + "role": msg.role, + "input_prompt": msg.display_content, + "input_images": input_images, + "input_audio": input_audio, + "input_documents": input_documents, + "created_at": msg.created_at.isoformat(), + } + elif msg.role == CHATML_ROLE_ASSISTANT: + output_images = ( + list( + MessageAttachment.objects.filter( + message=msg, metadata__mime_type__startswith="image/" + ).values_list("url", flat=True) + ) + or [] + ) + output_audio = ( + list( + MessageAttachment.objects.filter( + message=msg, metadata__mime_type__startswith="audio/" + ).values_list("url", flat=True) + ) + or [] + ) + output_documents = ( + list( + MessageAttachment.objects.filter( + message=msg, metadata__mime_type__startswith="application/pdf" + ).values_list("url", flat=True) + ) + or [] + ) + buttons, text, _ = parse_bot_html(msg.display_content) + api_messages[i] = { + "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": output_images, + "output_audio": output_audio, + "output_documents": output_documents, + "web_url": msg.saved_run.get_app_url(), + "user_message_id": msg.platform_msg_id, + "bot_message_id": msg.platform_msg_id.strip(MSG_ID_PREFIX), + } + return api_messages + class FeedbackQuerySet(models.QuerySet): def to_df( diff --git a/routers/root.py b/routers/root.py index f3829180a..536c5af63 100644 --- a/routers/root.py +++ b/routers/root.py @@ -7,6 +7,10 @@ from enum import Enum from time import time +from bots.models.convo_msg import ( + Conversation, + db_msgs_to_api_json, +) import gooey_gui as gui import sentry_sdk from fastapi import Depends, HTTPException, Query @@ -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, + integration_name: str = None, + conversation_id: str = None, ): from daras_ai_v2.bot_integration_widgets import get_web_widget_embed_code from routers.bots_api import api_hashids @@ -548,12 +556,35 @@ def chat_route( except (IndexError, BotIntegration.DoesNotExist): raise HTTPException(status_code=404) + conversationData = None + if conversation_id: + try: + conversation: Conversation = Conversation.objects.get( + id=api_hashids.decode(conversation_id)[0], + ) + mesasges = db_msgs_to_api_json(conversation.last_n_msgs()) + conversationData = dict( + id=conversation_id, + bot_id=integration_id, + timestamp=conversation.created_at.isoformat(), + user_id=conversation.web_user_id, + messages=mesasges, + ) + except (IndexError, Conversation.DoesNotExist): + # remove /share/conversation_id from the url and redirect to the root url + redirect_url = furl( + request.url.path.replace(f"/share/{conversation_id}", "/") + ) + return RedirectResponse(str(redirect_url), status_code=302) 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=conversationData), + ), "meta": raw_build_meta_tags( url=get_og_url_path(request), title=f"Chat with {bi.name}", From b434b294d3e3770808293013c9497a97275112e0 Mon Sep 17 00:00:00 2001 From: Anish Date: Wed, 10 Dec 2025 04:30:05 +0530 Subject: [PATCH 02/11] fix: send enableShareConversation as true for /cjhat route bots --- bots/models/convo_msg.py | 2 ++ routers/root.py | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bots/models/convo_msg.py b/bots/models/convo_msg.py index 121d97f16..15492fcf0 100644 --- a/bots/models/convo_msg.py +++ b/bots/models/convo_msg.py @@ -474,6 +474,7 @@ def db_msgs_to_entries(msgs: list["Message"]) -> list["ConversationEntry"]: ) return entries + class Message(models.Model): conversation = models.ForeignKey( "Conversation", on_delete=models.CASCADE, related_name="messages" @@ -595,6 +596,7 @@ def __str__(self): return f"{self.metadata.name} ({self.url})" return self.url + def db_msgs_to_api_json(msgs: list["Message"]) -> list[dict]: from daras_ai_v2.bots import parse_bot_html from routers.bots_api import MSG_ID_PREFIX diff --git a/routers/root.py b/routers/root.py index 536c5af63..602ad8012 100644 --- a/routers/root.py +++ b/routers/root.py @@ -583,7 +583,11 @@ def chat_route( "bi": bi, "embed_code": get_web_widget_embed_code( bi, - config=dict(mode="fullscreen", conversationData=conversationData), + config=dict( + mode="fullscreen", + enableShareConversation=True, + conversationData=conversationData, + ), ), "meta": raw_build_meta_tags( url=get_og_url_path(request), From b87614551c6917e74ecaa47bdf9f83fc877a6f59 Mon Sep 17 00:00:00 2001 From: Anish Date: Thu, 11 Dec 2025 14:08:30 +0530 Subject: [PATCH 03/11] fix: send references in db_msgs_to_api_json --- bots/models/convo_msg.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bots/models/convo_msg.py b/bots/models/convo_msg.py index 15492fcf0..bc23223b4 100644 --- a/bots/models/convo_msg.py +++ b/bots/models/convo_msg.py @@ -668,6 +668,7 @@ def db_msgs_to_api_json(msgs: list["Message"]) -> list[dict]: ) or [] ) + references = msg.saved_run.state.get("references") buttons, text, _ = parse_bot_html(msg.display_content) api_messages[i] = { "role": msg.role, @@ -683,6 +684,7 @@ def db_msgs_to_api_json(msgs: list["Message"]) -> list[dict]: "web_url": msg.saved_run.get_app_url(), "user_message_id": msg.platform_msg_id, "bot_message_id": msg.platform_msg_id.strip(MSG_ID_PREFIX), + "references": references, } return api_messages From 4e32c12b0e48974b6bf3d93f4847a3d46f0bbe82 Mon Sep 17 00:00:00 2001 From: Dev Aggarwal Date: Thu, 11 Dec 2025 17:27:15 +0400 Subject: [PATCH 04/11] refactor: merge duplicate code, return 404 on wrong Conversation ID --- bots/models/convo_msg.py | 89 +++++++++++----------------------------- routers/root.py | 42 +++++++++---------- 2 files changed, 45 insertions(+), 86 deletions(-) diff --git a/bots/models/convo_msg.py b/bots/models/convo_msg.py index bc23223b4..ff821288c 100644 --- a/bots/models/convo_msg.py +++ b/bots/models/convo_msg.py @@ -597,80 +597,43 @@ def __str__(self): return self.url -def db_msgs_to_api_json(msgs: list["Message"]) -> list[dict]: +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 - api_messages = [None] * len(msgs) - for i, msg in enumerate(msgs): + 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: - input_images = ( - list( - MessageAttachment.objects.filter( - message=msg, metadata__mime_type__startswith="image/" - ).values_list("url", flat=True) - ) - or [] - ) - - input_audio = ( - list( - MessageAttachment.objects.filter( - message=msg, metadata__mime_type__startswith="audio/" - ).values_list("url", flat=True) - ) - or "" - ) - # any document type other than audio/image - input_documents = ( - list( - MessageAttachment.objects.filter(message=msg) - .exclude( - Q(metadata__mime_type__startswith="image/") - | Q(metadata__mime_type__startswith="audio/") - ) - .values_list("url", flat=True) - ) - or [] + documents = list( + msg.attachments.exclude( + Q(metadata__mime_type__startswith="image/") + | Q(metadata__mime_type__startswith="audio/") + ).values_list("url", flat=True) ) - api_messages[i] = { + yield { "role": msg.role, "input_prompt": msg.display_content, - "input_images": input_images, - "input_audio": input_audio, - "input_documents": input_documents, + "input_images": images, + "input_audio": audio, + "input_documents": documents, "created_at": msg.created_at.isoformat(), } elif msg.role == CHATML_ROLE_ASSISTANT: - output_images = ( - list( - MessageAttachment.objects.filter( - message=msg, metadata__mime_type__startswith="image/" - ).values_list("url", flat=True) - ) - or [] - ) - output_audio = ( - list( - MessageAttachment.objects.filter( - message=msg, metadata__mime_type__startswith="audio/" - ).values_list("url", flat=True) - ) - or [] - ) - output_documents = ( - list( - MessageAttachment.objects.filter( - message=msg, metadata__mime_type__startswith="application/pdf" - ).values_list("url", flat=True) - ) - or [] - ) references = msg.saved_run.state.get("references") buttons, text, _ = parse_bot_html(msg.display_content) - api_messages[i] = { + yield { "role": msg.role, "created_at": msg.created_at.isoformat(), "status": "completed", @@ -678,15 +641,13 @@ def db_msgs_to_api_json(msgs: list["Message"]) -> list[dict]: "raw_output_text": [msg.content], "output_text": [text], "buttons": buttons, - "output_images": output_images, - "output_audio": output_audio, - "output_documents": output_documents, + "output_images": images, + "output_audio": audio, "web_url": msg.saved_run.get_app_url(), "user_message_id": msg.platform_msg_id, "bot_message_id": msg.platform_msg_id.strip(MSG_ID_PREFIX), "references": references, } - return api_messages class FeedbackQuerySet(models.QuerySet): diff --git a/routers/root.py b/routers/root.py index 602ad8012..96e590a81 100644 --- a/routers/root.py +++ b/routers/root.py @@ -7,10 +7,6 @@ from enum import Enum from time import time -from bots.models.convo_msg import ( - Conversation, - db_msgs_to_api_json, -) import gooey_gui as gui import sentry_sdk from fastapi import Depends, HTTPException, Query @@ -29,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 @@ -544,9 +544,9 @@ def chat_explore_route(request: Request): @app.get("/chat/{integration_name}-{integration_id}/share/{conversation_id}/") def chat_route( request: Request, - integration_id: str = None, - integration_name: str = None, - conversation_id: str = None, + 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 @@ -556,26 +556,24 @@ def chat_route( except (IndexError, BotIntegration.DoesNotExist): raise HTTPException(status_code=404) - conversationData = None if conversation_id: try: conversation: Conversation = Conversation.objects.get( id=api_hashids.decode(conversation_id)[0], ) - mesasges = db_msgs_to_api_json(conversation.last_n_msgs()) - conversationData = dict( - id=conversation_id, - bot_id=integration_id, - timestamp=conversation.created_at.isoformat(), - user_id=conversation.web_user_id, - messages=mesasges, - ) except (IndexError, Conversation.DoesNotExist): - # remove /share/conversation_id from the url and redirect to the root url - redirect_url = furl( - request.url.path.replace(f"/share/{conversation_id}", "/") - ) - return RedirectResponse(str(redirect_url), status_code=302) + raise HTTPException(status_code=404) + mesasges = 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=mesasges, + ) + else: + conversation_data = None + return templates.TemplateResponse( "chat_fullscreen.html", { @@ -586,7 +584,7 @@ def chat_route( config=dict( mode="fullscreen", enableShareConversation=True, - conversationData=conversationData, + conversationData=conversation_data, ), ), "meta": raw_build_meta_tags( From 4eb0fd14e9ffe1b717037769f7719e7a98386000 Mon Sep 17 00:00:00 2001 From: Anish Date: Tue, 16 Dec 2025 14:35:15 +0530 Subject: [PATCH 05/11] fix: send msg.content also as input_prompt --- bots/models/convo_msg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bots/models/convo_msg.py b/bots/models/convo_msg.py index ff821288c..52930719d 100644 --- a/bots/models/convo_msg.py +++ b/bots/models/convo_msg.py @@ -624,7 +624,7 @@ def db_msgs_to_api_json(msgs: list["Message"]) -> typing.Iterator[dict]: ) yield { "role": msg.role, - "input_prompt": msg.display_content, + "input_prompt": msg.display_content or msg.content, "input_images": images, "input_audio": audio, "input_documents": documents, From 19484d93e96caeeece213728bf12c0a9834a92bf Mon Sep 17 00:00:00 2001 From: Anish Date: Wed, 17 Dec 2025 12:37:11 +0530 Subject: [PATCH 06/11] feat: conversation share checkbox in bot integrations widgets --- daras_ai_v2/bot_integration_widgets.py | 4 ++++ routers/root.py | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) 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 96e590a81..f8a116925 100644 --- a/routers/root.py +++ b/routers/root.py @@ -583,7 +583,6 @@ def chat_route( bi, config=dict( mode="fullscreen", - enableShareConversation=True, conversationData=conversation_data, ), ), From 40abd00acef7435c8aef7d6703a26b6b15311782 Mon Sep 17 00:00:00 2001 From: Anish Date: Wed, 17 Dec 2025 12:49:09 +0530 Subject: [PATCH 07/11] fix: add check for enableShareConversation before fetching conversation --- routers/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/root.py b/routers/root.py index f8a116925..3dfb82b7f 100644 --- a/routers/root.py +++ b/routers/root.py @@ -556,7 +556,7 @@ def chat_route( except (IndexError, BotIntegration.DoesNotExist): raise HTTPException(status_code=404) - if conversation_id: + if conversation_id and bi.web_config_extras.get("enableShareConversation", False): try: conversation: Conversation = Conversation.objects.get( id=api_hashids.decode(conversation_id)[0], From a9bd738584b75c4de6365bc500069e64d36ed624 Mon Sep 17 00:00:00 2001 From: Anish Date: Thu, 8 Jan 2026 16:39:06 +0530 Subject: [PATCH 08/11] fix: handle when saved_run is None --- bots/models/convo_msg.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bots/models/convo_msg.py b/bots/models/convo_msg.py index 52930719d..eb35bf389 100644 --- a/bots/models/convo_msg.py +++ b/bots/models/convo_msg.py @@ -631,7 +631,12 @@ def db_msgs_to_api_json(msgs: list["Message"]) -> typing.Iterator[dict]: "created_at": msg.created_at.isoformat(), } elif msg.role == CHATML_ROLE_ASSISTANT: - references = msg.saved_run.state.get("references") + 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, @@ -643,7 +648,7 @@ def db_msgs_to_api_json(msgs: list["Message"]) -> typing.Iterator[dict]: "buttons": buttons, "output_images": images, "output_audio": audio, - "web_url": msg.saved_run.get_app_url(), + "web_url": web_url, "user_message_id": msg.platform_msg_id, "bot_message_id": msg.platform_msg_id.strip(MSG_ID_PREFIX), "references": references, From 6f392d22cfa359dbcc6f8456707ec98223ce278c Mon Sep 17 00:00:00 2001 From: Anish Date: Thu, 8 Jan 2026 16:40:34 +0530 Subject: [PATCH 09/11] fix: use removeprefix() for bot_message_id --- bots/models/convo_msg.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bots/models/convo_msg.py b/bots/models/convo_msg.py index eb35bf389..aaf189235 100644 --- a/bots/models/convo_msg.py +++ b/bots/models/convo_msg.py @@ -650,7 +650,8 @@ def db_msgs_to_api_json(msgs: list["Message"]) -> typing.Iterator[dict]: "output_audio": audio, "web_url": web_url, "user_message_id": msg.platform_msg_id, - "bot_message_id": msg.platform_msg_id.strip(MSG_ID_PREFIX), + "bot_message_id": msg.platform_msg_id + and msg.platform_msg_id.removeprefix(MSG_ID_PREFIX), "references": references, } From fc9bf8d6a358807272ce858514dbb884325b00cf Mon Sep 17 00:00:00 2001 From: Anish Date: Thu, 8 Jan 2026 16:45:44 +0530 Subject: [PATCH 10/11] fix: still load conversation if the feature is turned off --- routers/root.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/root.py b/routers/root.py index 3dfb82b7f..f8a116925 100644 --- a/routers/root.py +++ b/routers/root.py @@ -556,7 +556,7 @@ def chat_route( except (IndexError, BotIntegration.DoesNotExist): raise HTTPException(status_code=404) - if conversation_id and bi.web_config_extras.get("enableShareConversation", False): + if conversation_id: try: conversation: Conversation = Conversation.objects.get( id=api_hashids.decode(conversation_id)[0], From 083aa820f6b1dc377fcf833cab7910c5840fa06b Mon Sep 17 00:00:00 2001 From: Anish Date: Fri, 9 Jan 2026 13:48:50 +0530 Subject: [PATCH 11/11] fix: typo --- routers/root.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routers/root.py b/routers/root.py index f8a116925..c204dbb89 100644 --- a/routers/root.py +++ b/routers/root.py @@ -563,13 +563,13 @@ def chat_route( ) except (IndexError, Conversation.DoesNotExist): raise HTTPException(status_code=404) - mesasges = list(db_msgs_to_api_json(conversation.last_n_msgs())) + 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=mesasges, + messages=messages, ) else: conversation_data = None