Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ jobs:
- name: Install Poetry
uses: snok/install-poetry@a783c322200f0519c7926aa6faa857c4e23e9263

- name: Set up Node
uses: actions/setup-node@v7
with:
node-version: "20"

- name: Setup test infrastructure
run: |
cd integration_tests
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ build-django-static: ## Build django-app static files
test-integration:
docker compose down opensearch db sso minio
docker compose up -d --wait opensearch db sso minio
cd django_app && \
cd django_app/frontend && \
npm ci && \
npm run build && \
cd .. && \
poetry install && \
poetry run playwright install --with-deps chromium && \
DJANGO_ALLOW_ASYNC_UNSAFE=1 poetry run pytest tests/playwright -rP --browser chromium --tracing retain-on-failure --video on --screenshot on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
@forward './collapsible-menu.scss';
@forward './divider.scss';
@forward './editable-text.scss';
@forward './feedback.scss';
@forward './header.scss';
@forward './list-row.scss';
@forward './loading-message.scss';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,15 @@ ids-chat-message {
.ids-chat-message__container {
gap: var(--chat-message-gap);
}

.ids-chat-message__post_message_actions_container {
display: flex;
justify-content: space-between;
border-bottom: 1px;
border-color: var(--gds-mid-grey);
border-bottom-style: solid;
}

.ids-chat-message__copy-text-container {
min-width: fit-content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.feedback-form-container {
width: 100%;
// min height prevents layout shift as feedback buttons are loaded
min-height: 4.5rem;
}

.feedback-button-container {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}

.feedback-actions {
display: flex;
gap: 10px;
flex-wrap: nowrap;
}

.feedback-button {
width: auto;
white-space: nowrap;
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ chat-history[data-initialised] .rb-chat-history__actions-button {
flex-wrap: wrap;
border: none;
gap: 15px;
padding: 0px;;
padding: 0px;
}

.feedback__heading {
Expand All @@ -61,7 +61,7 @@ chat-history[data-initialised] .rb-chat-history__actions-button {
}

.feedback__text-area {
margin-top: 0.5rem; /* Adjust as needed */
margin-top: 0.5rem;
}
.feedback__text-input {
display: block;
Expand Down Expand Up @@ -298,12 +298,6 @@ main:has(.iai-chat-bubble[data-role="ai"]) .exit-feedback {
gap: 18px;
}


.feedback-button-container {
display: flex;
gap: 8px;
}

.ids-message-container {
display: flex;
flex-direction: column;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import { emitEvent, Events, listenEvent } from "../../../interaction_design_system/ids/events";
import { getActiveToolId, sanitizeHtml } from "../../utils";
import { ChatMessage } from "./chat-message";
import htmx from "htmx.org";


const STATE = {
EMPTY: "empty",
Expand Down Expand Up @@ -280,12 +282,17 @@
if (!this.messageContainer) return console.error("Missing message container");
if (!this.currentStream) return console.error("No active stream");

const html = sanitizeHtml(response.html);
this.messageContainer.insertAdjacentHTML("beforeend", html);
// Sanitising here strips the feedback hx-* attributes.
// LLM output is sanitised at its own boundary in StreamedContent.
this.messageContainer.insertAdjacentHTML("beforeend", response.html);

const message = this.getMessage(response.chat_message_id);

if (message) htmx.process(message);

if (response.chat_message_role === "ai") {
this.currentStream.messageId = response.chat_message_id;
this.getMessage(response.chat_message_id)?.focus();
message?.focus();
}
}

Expand All @@ -308,8 +315,14 @@
* @param {MessageCompleteResponse} response
*/
handleMessageComplete(response) {
this.getMessage(response.chat_message_id)?.complete(response.html);
const message = this.getMessage(response.chat_message_id);
message?.complete(response.html);
if (this.currentStream) this.currentStream.title = response.title;
console.log('complete id:', response.chat_message_id, 'found:', message); // <-- add this

// Feedback chrome in the message shell waits on this event before
// firing its hx-get (see _feedback_container.html hx-trigger).
message?.dispatchEvent(new CustomEvent("streaming-complete", { bubbles: true }));
}


Expand Down
3 changes: 2 additions & 1 deletion django_app/redbox_app/jinja2.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from django.urls import reverse
from django.utils.timezone import template_localtime
from markdown_it import MarkdownIt
from waffle import flag_is_active
from waffle import flag_is_active, switch_is_active

from redbox_app.redbox_core import flags
from redbox_app.redbox_core.types import APPROVED_FILE_EXTENSIONS
Expand Down Expand Up @@ -164,6 +164,7 @@ def environment(**options):
"google_analytics_iframe_src": settings.GOOGLE_ANALYTICS_IFRAME_SRC,
"get_messages": messages.get_messages,
"flag_is_active": flag_is_active,
"switch_is_active": switch_is_active,
"flags": flags,
"get_menu_items": get_menu_items,
"product_name": get_product_name,
Expand Down
1 change: 1 addition & 0 deletions django_app/redbox_app/redbox_core/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
ENABLE_INVEST_LENS = "enable_invest_lens"
RESET_SSO_SYNC_SESSION = "reset_sso_sync_session"
ENABLE_CHATS_REDESIGN = "enable_chats_redesign"
ENABLE_FEEDBACK_REDESIGN = "enable_feedback_redesign"
29 changes: 28 additions & 1 deletion django_app/redbox_app/redbox_core/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from django import forms
from django.contrib.auth import get_user_model

from redbox_app.redbox_core.models import Tool, ToolAccessRule, UserTool
from redbox_app.redbox_core.models import ChatMessageFeedback, Tool, ToolAccessRule, UserTool
from redbox_app.redbox_core.services import url as url_service

User = get_user_model()
Expand Down Expand Up @@ -392,3 +392,30 @@ def clean(self):
self.cleaned_data["user_ids"] = raw_user_ids

return cleaned_data


class ChatMessageFeedbackForm(forms.ModelForm):
is_positive = forms.BooleanField(required=False)

reason = forms.MultipleChoiceField(
choices=ChatMessageFeedback.Reason.choices,
required=False,
widget=forms.CheckboxSelectMultiple,
)

class Meta:
model = ChatMessageFeedback
fields = ("is_positive", "reason", "detail")
widgets: ClassVar[Mapping[str, forms.Widget]] = {
"detail": forms.Textarea(attrs={"rows": 3}),
}

def clean(self):
cleaned = super().clean()
is_positive = cleaned.get("is_positive")
if is_positive:
if cleaned.get("reason"):
self.add_error("reason", "Positive feedback cannot have reasons.")
if cleaned.get("detail"):
self.add_error("detail", "Positive feedback cannot have detail.")
return cleaned
1 change: 1 addition & 0 deletions django_app/redbox_app/redbox_core/services/chats.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def get_context(request: HttpRequest, chat_id: UUID | None = None, slug: str | N
"redbox_api_key": settings.REDBOX_API_KEY,
"enable_dictation_flag_is_active": flag_is_active(request, flags.ENABLE_DICTATION),
"enable_chats_redesign": flag_is_active(request, flags.ENABLE_CHATS_REDESIGN),
"enable_feedback_redesign": flag_is_active(request, flags.ENABLE_FEEDBACK_REDESIGN),
**file_context,
"urls": urls,
"errors": {"upload_doc": []},
Expand Down
3 changes: 3 additions & 0 deletions django_app/redbox_app/redbox_core/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
remove_doc_view,
upload_document,
)
from redbox_app.redbox_core.views.feedback_views import chat_message_feedback, get_feedback_buttons
from redbox_app.redbox_core.views.file_views import (
file_icon_view,
file_ingest_errors_view,
Expand Down Expand Up @@ -106,6 +107,7 @@
"add_team_member_row_view",
"add_team_member_view",
"aws_credentials_api",
"chat_message_feedback",
"create_team_view",
"delete_document",
"delete_team_member_row_view",
Expand All @@ -120,6 +122,7 @@
"file_icon_view",
"file_ingest_errors_view",
"file_status_api_view",
"get_feedback_buttons",
"health",
"homepage_view",
"message_view_pre_alpha",
Expand Down
77 changes: 77 additions & 0 deletions django_app/redbox_app/redbox_core/views/feedback_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from django.contrib.auth.decorators import login_required
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.views.decorators.http import require_http_methods
from waffle import switch_is_active

from redbox_app.redbox_core import flags
from redbox_app.redbox_core.forms import ChatMessageFeedbackForm
from redbox_app.redbox_core.models import ChatMessage, ChatMessageFeedback

FEEDBACK_SWITCH = flags.ENABLE_FEEDBACK_REDESIGN
FORM_TEMPLATE = "chat/message/feedback/_feedback-form.html"
BUTTONS_TEMPLATE = "chat/message/feedback/_feedback_buttons.html"
THANKS_TEMPLATE = "chat/message/feedback/_feedback-thanks.html"


@login_required
@require_http_methods(["GET"])
def get_feedback_buttons(request, message_id):
if not switch_is_active(FEEDBACK_SWITCH):
raise Http404

message = get_object_or_404(
ChatMessage.objects.filter(chat__user=request.user),
id=message_id,
)

instance = ChatMessageFeedback.objects.filter(message=message).first()
context = {"message_id": message.id, "message": message}

if instance is None:
return render(request, BUTTONS_TEMPLATE, context)

return render(request, THANKS_TEMPLATE, context)


@login_required
@require_http_methods(["POST", "DELETE"])
def chat_message_feedback(request, message_id):
if not switch_is_active(FEEDBACK_SWITCH):
raise Http404

message = get_object_or_404(
ChatMessage.objects.filter(chat__user=request.user),
id=message_id,
)

instance = ChatMessageFeedback.objects.filter(message=message).first()
context = {"message_id": message.id, "message": message}

if request.method == "DELETE":
if instance is not None:
instance.delete()
return render(request, BUTTONS_TEMPLATE, context)

form = ChatMessageFeedbackForm(request.POST, instance=instance)
if not form.is_valid():
response = render(request, FORM_TEMPLATE, {**context, "form": form}, status=422)
response["HX-Reswap"] = "innerHTML"
return response

feedback, _ = ChatMessageFeedback.objects.update_or_create(
message=message,
defaults={
"is_positive": form.cleaned_data["is_positive"],
"reason": form.cleaned_data["reason"],
"detail": form.cleaned_data["detail"],
},
)

show_form = request.GET.get("show_form") == "true"
if show_form:
form = ChatMessageFeedbackForm(instance=feedback)
return render(request, FORM_TEMPLATE, {**context, "form": form})

return redirect(reverse("chat-message-feedback-buttons", kwargs={"message_id": message_id}))
4 changes: 3 additions & 1 deletion django_app/redbox_app/templates/chat/chat_feed.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,16 @@ <h1 class="govuk-visually-hidden">New chat for {{ tool if tool else product_name
{# SSR messages #}
<div role="log">
<ol id="chat-message-list" aria-label="AI agent conversation" class="govuk-list ids-chat-message__container ids-flex-column">

{% for message in messages %}
{{ message_box(
role=message.role,
text=message.text,
message_id=message.id,
selected_files=message.unique_selected_files(),
resources=message.resources,
route=message.route
route=message.route,
initial_page_load=True
) }}
{% endfor %}
</ol>
Expand Down
6 changes: 0 additions & 6 deletions django_app/redbox_app/templates/chat/message/feedback.html

This file was deleted.

Loading
Loading