.container-fluid data-controller="question-assistant2" .row .col-md-9.expanded = render_breadcrumbs([ \ { name: 'Home', path: projects_path }, \ { name: @project.title, path: project_path(@project) }, \ { name: "Assistant V2" } \ ]) #questionAssistant2.position-relative.border.rounded.shadow-sm.bg-white.d-flex.flex-row.overflow-hidden style="height: calc(85svh);" / Left Column (Acts as the dedicated track and the push/pull sidebar) .bg-light.border-end.d-flex.flex-column.h-100 style="flex-shrink: 0;" / Top Dedicated Area for Toggle Button (Always visible) .p-2.d-flex.align-items-center.justify-content-start style="z-index: 5;" / Span wrapper handles the tooltip so it doesn't conflict with the button's collapse toggle span data-bs-toggle="tooltip" data-bs-trigger="hover" data-bs-placement="right" title="Toggle Chats" button.btn.btn-sm.bg-white.border.shadow-sm.text-dark.d-flex.align-items-center.justify-content-center data-bs-toggle="collapse" data-bs-target="#chatHistorySidebar" aria-expanded="true" aria-controls="chatHistorySidebar" style="border-radius: 8px; width: 36px; height: 36px; padding: 0;" i.bi.bi-list.fs-5 / Collapsible Sidebar Area (Opens below the button) #chatHistorySidebar.collapse.collapse-horizontal.show.flex-grow-1 / Inner wrapper to enforce width and prevent squishing during animation .d-flex.flex-column.h-100 style="width: 260px;" .d-flex.align-items-center.justify-content-between.px-3.pb-3.pt-1 strong.mb-0 Recent Chats = link_to assistant_conversations_path(raggable_id: @project.id, raggable_type: "Project", project_id: @project.id), class: "btn btn-sm btn-outline-primary border-0 rounded-circle bg-white shadow-sm", data: { turbo_method: :post }, title: "New Chat" do i.feather-16 data-feather="plus" / Chat List ul.list-group.list-group-flush.flex-grow-1.p-2.border-top style="overflow-y: auto;" - @conversations.each do |conversation| li class="assistant list-group-item p-1 mb-1 border-0 rounded-3 #{'active shadow-sm' if @conversation && conversation.id == @conversation.id}" style="background-color: transparent;" .d-flex.justify-content-between.align-items-center = link_to "Chat ##{conversation.id}", assistant_v2_project_path(@project, conversation_id: conversation.id), class: "text-decoration-none text-dark flex-grow-1 p-2 rounded-2 list-item-link" = link_to assistant_conversation_path(conversation, project_id: @project.id, raggable_type: "Project"), class: "btn btn-sm text-muted border-0 hover-danger", data: { turbo_confirm: "Are you sure?", turbo_method: :delete }, title: "Delete" do i.bi.bi-trash3 / Main Chat Container - if !@conversation.outdated ruby: is_empty = @conversation.assistant_messages.none? .flex-grow-1.d-flex.flex-column.position-relative.p-3 data-question-assistant2-target="chatContainer" class=("empty-state" if is_empty) style="min-width: 0;" / Messages Area .chat-messages.flex-grow-1.px-3.pb-3.mb-3#v2messages data-question-assistant2-target="messages" style="overflow-y: auto; scroll-behavior: smooth; border-radius: 12px; #{'display: none;' if is_empty}" = turbo_stream_from [@conversation, "messages"] - @conversation.assistant_messages.order(:created_at).each do |message| = render partial: "assistant_conversations/message", locals: { message: } / Input & Filters Area .chat-input-section.mt-auto data-question-assistant2-target="inputSection" = form_with(url: message_assistant_conversation_path(@conversation), html: { data: { "question-assistant2-target": "form" }, class: "mx-auto w-100", style: "max-width: 800px;" }) do |form| / Unified Input & Filters Container .unified-input-wrapper.position-relative.shadow-sm.bg-white.border.rounded-4.mb-3 style="z-index: 5; transition: box-shadow 0.2s ease, border-color 0.2s ease;" / Text Input Area .position-relative = form.text_area :question, class: "form-control border-0 bg-transparent shadow-none", rows: "1", placeholder: "Ask anything about the respondents...", style: "resize: none; padding: 1rem 3.5rem 1rem 1.25rem; min-height: 56px; max-height: 200px; overflow-y: auto;", data: { "question-assistant2-target": "input", action: "input->question-assistant2#autoResize keydown->question-assistant2#handleKeydown" } button.btn.btn-success.position-absolute.rounded-circle.d-flex.align-items-center.justify-content-center.p-0.border-0.shadow-none type="submit" data-question-assistant2-target="submitBtn" style="width: 36px; height: 36px; bottom: 10px; right: 10px; background-color: #8DD262; transition: all 0.2s ease;" disabled="disabled" i.bi.bi-arrow-up.fs-5.text-white / Minimalist Toggle Header (Only takes space once chat starts and filters collapse) .px-3.pb-2 class=("d-none" if is_empty) data-question-assistant2-target="mainFiltersHeader" button.btn.btn-sm.btn-link.text-decoration-none.text-muted.p-0 type="button" data-bs-toggle="collapse" data-bs-target="#mainFilters" data-action="click->question-assistant2#toggleMainFilters" style="font-size: 0.85rem;" i.bi.bi-chevron-down.me-1 data-question-assistant2-target="mainToggleIcon" span data-question-assistant2-target="mainToggleText" Show Filters / Filters Tray .collapse#mainFilters class=("show" if is_empty) data-question-assistant2-target="mainFilters" .border-top.p-3.bg-light.rounded-bottom-4 .row.g-2.align-items-center .col-md-5 label.form-label.text-muted.small.fw-semibold.mb-1 i.bi.bi-person.me-1 | Respondents select id="respondentSelect" multiple="multiple" name="respondent_ids[]" class="form-select" data-question-assistant2-target="respondentSelect" option value="all" selected="true" All optgroup label="Specific Respondents" - @conversation.raggable.respondents.each do |r| option value=r.id = r.name .col-md-2.text-center.d-flex.align-items-center.justify-content-center.pt-3.mt-3 span.badge.bg-white.text-primary.border OR .col-md-5 label.form-label.text-muted.small.fw-semibold.mb-1 i.bi.bi-tags.me-1 | Tags select#tagsSelect multiple="multiple" name="tags[]" class="form-control" data-question-assistant2-target="tagSelect" - respondent_tag_ids = @conversation.raggable.respondents.flat_map(&:tag_ids).uniq - @project.tag_categories.each do |category| - category_tags = category.tags.where(id: respondent_tag_ids) - if category_tags.any? optgroup label=category.name - category_tags.each do |tag| option value=tag.id = tag.name .d-flex.justify-content-between.align-items-center.mt-3.pt-2.border-top button.btn.btn-sm.btn-link.text-decoration-none.text-muted.p-0 type="button" data-bs-toggle="collapse" data-bs-target="#additionalFilters" data-action="click->question-assistant2#toggleAdditionalFilters" i.bi.bi-chevron-down.me-1 data-question-assistant2-target="toggleIcon" span Show Additional Filters small.text-muted i.bi.bi-info-circle.me-1 | Filter context before asking .collapse#additionalFilters.mt-3 .border-top.pt-3 label.form-label.text-muted.small.fw-semibold.mb-1 i.bi.bi-list.me-1 | Section Limit select#sectionSelect name="section" class="form-control" data-question-assistant2-target="sectionSelect" option value="" disabled="" selected="" hidden="" Select a section - @project.master_sections.order(:position).each do |section| option value="#{section.id}" = section.name - else .flex-grow-1.d-flex.flex-column.position-relative.p-3 style="min-width: 0;" .chat-messages.flex-grow-1.border.rounded.px-3.pb-3.mb-3#v2messages style="overflow-y: auto; background-color: #F9FAFB;" - @conversation.assistant_messages.order(:created_at).each do |message| = render partial: "assistant_conversations/message", locals: { message: } .card.border-warning.bg-light.shadow-sm.rounded-4 .card-body.p-3 .d-flex.align-items-center.justify-content-between .d-flex.align-items-center .icon-stack.me-3 i.bi.bi-shield-exclamation.text-warning.fs-3 div h6.mb-0.fw-bold Read-Only Mode small.text-muted Source data has changed. Start a new chat to continue. = link_to assistant_conversations_path(raggable_id: @project.id, raggable_type: "Project", project_id: @project.id), class: "btn btn-primary btn-sm px-4 rounded-pill shadow-sm", data: { turbo_method: :post } do | New Chat #citation.modal.fade data-bs-backdrop="static" data-bs-keyboard="true" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true" .modal-dialog.modal-lg .modal-content .modal-header.bg-light h5.modal-title.text-primary Source button type="button" class="btn-close btn-close-black" data-bs-dismiss="modal" aria-label="Close" .modal-body style="max-height: 70vh; overflow-y: auto;" #citationContent