Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion bots/models/convo_msg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -597,6 +597,59 @@ 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:
references = msg.saved_run.state.get("references")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Null pointer when saved_run is None

The code accesses msg.saved_run.state.get("references") without checking if saved_run is None. The Message.saved_run field is defined with null=True, default=None, so assistant messages may have no associated saved run, which would cause an AttributeError when accessing .state.

Fix in Cursor Fix in Web

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": msg.saved_run.get_app_url(),
"user_message_id": msg.platform_msg_id,
"bot_message_id": msg.platform_msg_id.strip(MSG_ID_PREFIX),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Null pointer when platform_msg_id is None

The code calls msg.platform_msg_id.strip(MSG_ID_PREFIX) without checking if platform_msg_id is None. The Message.platform_msg_id field is defined with null=True, default=None, so this will raise an AttributeError when the field is None.

Fix in Cursor Fix in Web

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Wrong string method strips characters instead of prefix

The code uses str.strip(MSG_ID_PREFIX) but strip() removes all characters in the argument from both ends of the string, not a prefix. With MSG_ID_PREFIX = "web-", this removes any 'w', 'e', 'b', or '-' characters from both ends rather than just the "web-" prefix. Other places in the codebase correctly use .removeprefix() or .lstrip() for this purpose.

Fix in Cursor Fix in Web

"references": references,
}


class FeedbackQuerySet(models.QuerySet):
def to_df(
self, tz=pytz.timezone(settings.TIME_ZONE), row_limit=10000
Expand Down
4 changes: 4 additions & 0 deletions daras_ai_v2/bot_integration_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", {})
Expand All @@ -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"]
Expand Down
36 changes: 34 additions & 2 deletions routers/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -548,12 +556,36 @@ def chat_route(
except (IndexError, BotIntegration.DoesNotExist):
raise HTTPException(status_code=404)

if conversation_id and bi.web_config_extras.get("enableShareConversation", False):
try:
conversation: Conversation = Conversation.objects.get(
id=api_hashids.decode(conversation_id)[0],
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Conversation access lacks bot integration authorization check

The conversation is fetched only by its ID without verifying it belongs to the bi (bot integration) specified in the URL. Since Conversation has a bot_integration foreign key, a malicious user could craft a share URL with one integration's ID but another conversation's ID, potentially exposing private conversation data from a different integration. The query needs to filter by bot_integration=bi as well.

Fix in Cursor Fix in Web

except (IndexError, Conversation.DoesNotExist):
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",
{
"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}",
Expand Down