.container-fluid .row .col-md-3 = render partial: "respondent_categories/sidebar" .col-md-9 = render_breadcrumbs([ \ { name: 'Home', path: projects_path }, \ { name: @project.title, path: project_path(@project) }, \ { name: @respondent_category.name, path: [@project, @respondent_category] }, \ { name: "Project Setup" } \ ]) .setup-container.bg-white.rounded-4.shadow-sm.border.p-4.mb-5 .d-flex.align-items-center.justify-content-between.mb-4 div h2.fw-bold.mb-1 Project Setup p.text-muted.mb-0 Configure your AI moderator and manage sub-project documents. ruby: active_tab = if params[:tab] == "documents" "documents" elsif params[:tab] == "dashboard" "dashboard" else "moderator" end / Tab Navigation ul.nav.nav-tabs.project-setup-tabs.mb-4 role="tablist" li.nav-item role="presentation" button.nav-link id="moderator-tab" data-bs-toggle="tab" data-bs-target="#moderator-pane" type="button" role="tab" class=(active_tab == "moderator" ? "active" : "") = lucide_icon "bot", size: 18, class: "me-2" | AI Moderator - if @ai_moderator.persisted? li.nav-item role="presentation" button.nav-link id="dashboard-tab" data-bs-toggle="tab" data-bs-target="#dashboard-pane" type="button" role="tab" class=(active_tab == "dashboard" ? "active" : "") = lucide_icon "bar-chart-2", size: 18, class: "me-2" | Dashboard li.nav-item role="presentation" button.nav-link id="documents-tab" data-bs-toggle="tab" data-bs-target="#documents-pane" type="button" role="tab" class=(active_tab == "documents" ? "active" : "") = lucide_icon "folder-open", size: 18, class: "me-2" | Documents - if @research_documents.any? span.badge.rounded-pill.bg-light.text-dark.ms-2 border="1" = @research_documents.count / Tab Content .tab-content / Tab 1: AI Moderator .tab-pane.fade id="moderator-pane" role="tabpanel" tabindex="0" class=(active_tab == "moderator" ? "show active" : "") - if @ai_moderator.persisted? .moderator-summary .d-flex.align-items-center.mb-4 .ai-success-icon.me-3 = lucide_icon "check-circle", size: 32, class: "text-success" div h4.fw-bold.mb-1 AI Moderator Initialized p.text-muted.mb-0 The AI Moderator for this sub-project is active. .moderator-launch-box.p-4.rounded-4.mb-4 .d-flex.align-items-center.justify-content-between.flex-wrap.gap-3 div p.text-uppercase.text-muted.small.fw-semibold.mb-1 Launch Link p.mb-0.moderator-launch-url = @ai_moderator.launch_link .d-flex.gap-2 a href=@ai_moderator.launch_link target="_blank" rel="noopener" class="btn btn-primary rounded-pill px-4" = lucide_icon "external-link", size: 16, class: "me-2" | Open button.btn.btn-outline-primary.rounded-pill.px-3 type="button" data-controller="copy-link" data-copy-link-text-value=@ai_moderator.launch_link data-action="click->copy-link#copy" = lucide_icon "copy", size: 16 h6.fw-bold.text-muted.text-uppercase.small.mb-3 Configuration .row.g-3 .col-md-6 .moderator-config-card.p-3.rounded-4.border p.text-muted.small.mb-1 Research Objectives p.mb-0.fw-medium = @ai_moderator.research_objectives .col-md-6 .moderator-config-card.p-3.rounded-4.border p.text-muted.small.mb-1 Target Group p.mb-0.fw-medium = @ai_moderator.target_group .col-md-6 .moderator-config-card.p-3.rounded-4.border p.text-muted.small.mb-1 Opening Statement p.mb-0.fw-medium = @ai_moderator.opening_stmt .col-md-6 .moderator-config-card.p-3.rounded-4.border p.text-muted.small.mb-1 Closing Statement p.mb-0.fw-medium = @ai_moderator.closing_stmt .col-md-6 .moderator-config-card.p-3.rounded-4.border p.text-muted.small.mb-1 Sample Size p.mb-0.fw-medium = @ai_moderator.sample_size .col-md-6 .moderator-config-card.p-3.rounded-4.border p.text-muted.small.mb-1 Moderator Identity p.mb-0.fw-medium = @ai_moderator.moderator_identity.titleize - else = simple_form_for @ai_moderator, url: initialize_integration_project_respondent_category_ai_moderators_path(@project, @respondent_category), html: { multipart: true, class: "ai-moderator-form" } do |f| - if @ai_moderator.errors.any? .setup-error-alert.d-flex.align-items-start.p-4.rounded-4.mb-4 .setup-error-icon.d-flex.align-items-center.justify-content-center.flex-shrink-0.me-3 = lucide_icon "alert-triangle", size: 20 div h6.fw-bold.mb-1 We couldn't initialize the moderator ul.mb-0.ps-3 - @ai_moderator.errors.full_messages.each do |msg| li.setup-error-msg = msg .row.g-4 .col-md-6 = f.input :research_objectives, label: "Research Objectives", input_html: { rows: 4, class: "bg-light border-0" }, placeholder: "What are the main goals of this research?" .col-md-6 = f.input :target_group, label: "Target Group", input_html: { rows: 4, class: "bg-light border-0" }, placeholder: "Describe the demographics and traits of the respondents." .col-md-6 = f.input :opening_stmt, label: "Opening Statement", input_html: { rows: 3, class: "bg-light border-0" }, placeholder: "How should the moderator greet the respondent?" .col-md-6 = f.input :closing_stmt, label: "Closing Statement", input_html: { rows: 3, class: "bg-light border-0" }, placeholder: "How should the session end?" .col-md-6 = f.input :sample_size, label: "Sample Size", as: :integer, input_html: { class: "bg-light border-0" } .col-md-6 = f.input :moderator_identity, label: "Moderator Identity", as: :select, collection: AiModerator.moderator_identities.keys.map { |k| [k.titleize, k] }, include_blank: false, input_html: { class: "bg-light border-0 form-select" } .divider-line.my-4 h5.fw-bold.mb-3 Discussion Guide .upload-guide-box.p-4.rounded-4.bg-light.border.mb-4 - if @existing_guide .alert.alert-info.d-flex.align-items-start.border-0.bg-secondary.text-primary.rounded-3 = lucide_icon "info", size: 20, class: "me-2 mt-1 flex-shrink-0" div strong.d-block A discussion guide is already uploaded. small You can upload a new one below to replace the existing guide, or leave this field blank to keep the current one. label.form-label.fw-medium for="discussion_guide_file" Upload Document (PDF/DOCX) input.form-control.border-0.bg-white.shadow-sm type="file" name="discussion_guide_file" id="discussion_guide_file" accept=".pdf,.docx" small.text-muted.d-block.mt-2 = lucide_icon "check", size: 14, class: "me-1" | This file will automatically be set as the Discussion Guide for this sub-project. .d-flex.justify-content-end.mt-4 = f.button :submit, "Get Started", class: "btn btn-primary px-5 py-2 fw-medium rounded-pill shadow-sm" / Tab 2: Dashboard - if @ai_moderator.persisted? .tab-pane.fade id="dashboard-pane" role="tabpanel" tabindex="0" class=(active_tab == "dashboard" ? "show active" : "") div data-controller="moderator-dashboard" data-moderator-dashboard-url-value=moderator_dashboard_project_respondent_category_path(@project, @respondent_category) div data-moderator-dashboard-target="loading" class="text-center py-5" .spinner-border.text-primary role="status" p.text-muted.mt-3.mb-0 Loading dashboard... div data-moderator-dashboard-target="error" class="text-center py-5 d-none" = lucide_icon "alert-circle", size: 40, class: "text-muted mb-3" p.text-muted.mb-0 Unable to load dashboard data right now. button.btn.btn-outline-primary.btn-sm.mt-3 type="button" data-action="click->moderator-dashboard#fetchStats" | Try Again .row.g-4.d-none data-moderator-dashboard-target="content" .col-md-3 .dashboard-stat-card.p-4.rounded-4.border .dashboard-stat-icon.mb-3 = lucide_icon "check-circle", size: 22 h3.fw-bold.mb-1 data-moderator-dashboard-target="completed" — p.text-muted.mb-0.small Interviews Completed .col-md-3 .dashboard-stat-card.p-4.rounded-4.border .dashboard-stat-icon.dashboard-stat-icon--progress.mb-3 = lucide_icon "loader", size: 22 h3.fw-bold.mb-1 data-moderator-dashboard-target="inProgress" — p.text-muted.mb-0.small In Progress .col-md-3 .dashboard-stat-card.p-4.rounded-4.border .dashboard-stat-icon.dashboard-stat-icon--duration.mb-3 = lucide_icon "clock", size: 22 h3.fw-bold.mb-1 span data-moderator-dashboard-target="avgDuration" — span.fs-6.fw-normal.text-muted | min p.text-muted.mb-0.small Average Duration .col-md-3 .dashboard-stat-card.p-4.rounded-4.border .dashboard-stat-icon.dashboard-stat-icon--rate.mb-3 = lucide_icon "percent", size: 22 h3.fw-bold.mb-1 span data-moderator-dashboard-target="completionRate" — | % p.text-muted.mb-0.small Completion Rate / Tab 3: Documents .tab-pane.fade id="documents-pane" role="tabpanel" tabindex="0" class=(active_tab == "documents" ? "show active" : "") = render partial: "research_documents/documents_list"