Răsfoiți Sursa

changes for the attr extraction multiple value and style and size

VISHAL BHANUSHALI 3 luni în urmă
părinte
comite
321f112b8a

+ 80 - 17
content_quality_tool_public/static/css/attr-extraction.css

@@ -2,7 +2,7 @@
     --text: #0b1220;           /* primary text on white */
     --muted: #5b6b80;          /* secondary text */
     --brand: #2563eb;          /* blue */
-    --brand-2: #14b8a6;        /* teal */
+    --brand-2: #27B7C1;        /* teal */
     --accent: #7c3aed;         /* violet */
     --danger: #ef4444;         /* red */
     --success: #16a34a;        /* green */
@@ -15,6 +15,12 @@
     --zebra: #fafbfc;
 }
 
+.form-check-input:checked{
+    background-color: #49B5E0;
+    border-color: #49B5E0
+;
+}
+
 /* * { box-sizing: border-box; } */
 html, body { height: 100%; }
 body {
@@ -29,25 +35,25 @@ body {
 
 header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: center; margin-bottom: 20px; }
 .title { display: flex; align-items: center; gap: 14px; }
-.title-logo { width: 44px; height: 44px; border-radius: 12px; background: linear-gradient(135deg, var(--brand), var(--text)); display: grid; place-items: center; box-shadow: var(--shadow); }
+.title-logo { width: 44px; height: 44px; border-radius: 12px; background: linear-gradient(135deg, var(--brand), var(--brand-2)); display: grid; place-items: center; box-shadow: var(--shadow); }
 .title-logo svg { filter: drop-shadow(0 2px 6px rgba(0,0,0,.15)); }
 h1 { font-size: 1.6rem; margin: 0; letter-spacing: .3px; }
 .sub { color: var(--muted); font-size: .95rem; margin-top: 4px; }
 
 .actions { display: flex; gap: 10px; flex-wrap: wrap; }
-button, .btn {
+/* button, .btn {
     appearance: none; border: 1px solid var(--border); cursor: pointer;
     padding: 10px 14px; border-radius: 10px; font-weight: 600;
     color: var(--text); background: #ffffff; transition: transform .06s ease, box-shadow .2s ease, border-color .2s ease;
     box-shadow: var(--shadow);
-}
-.btn:hover{
+} */
+/* .btn:hover{
     color: black !important;
-}
-button:hover { transform: translateY(-1px); border-color: #cbd5e1; }
-.btn-secondary { background: #f7fafc; }
-.btn-ghost { background: transparent; color: var(--muted); border-style: dashed; }
-.btn-success { background: linear-gradient(135deg, #eef2ff, #e6fffa); color: var(--text); }
+} */
+/* button:hover { transform: translateY(-1px); border-color: #cbd5e1; } */
+/* .btn-secondary { background: #f7fafc; } */
+/* .btn-ghost { background: transparent; color: var(--muted); border-style: dashed; } */
+/* .btn-success { background: linear-gradient(135deg, #eef2ff, #e6fffa); color: var(--text); } */
 
 /* Single-column layout */
 .grid { display: grid; grid-template-columns: 1fr; gap: 18px; }
@@ -70,7 +76,7 @@ button:hover { transform: translateY(-1px); border-color: #cbd5e1; }
 .list { display: grid; gap: 12px; }
 .product { display: grid; grid-template-columns: 64px 1fr 1fr auto; gap: 12px; align-items: start; padding: 10px; border-radius: 12px; background: #ffffff; border: 1px solid var(--border); cursor: pointer; }
 .product:hover { border-color: var(--row-hover); }
-.product.selected { outline: 2px solid var(--text); box-shadow: 0 0 0 3px rgba(20,184,166,.15); }
+.product.selected { outline: 2px solid var(--brand-2); box-shadow: 0 0 0 3px rgba(20,184,166,.15); }
 
 .thumb { width: 64px; height: 64px; border-radius: 10px; overflow: hidden; position: relative; background: #f3f4f6; border:1px solid var(--border); }
 .thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
@@ -93,7 +99,7 @@ input[type="checkbox"] { width: 18px; height: 18px; }
 .chip { background: var(--chip); border: 1px solid var(--border); border-radius: 999px; padding: 6px 9px; font-size: .86rem; display: inline-flex; gap: 8px; align-items: center; color: #1f2937; }
 .chip .k { color:#475569; }
 .chip .v { color:#111827; font-weight:600; }
-.chip.new { outline: 2px solid var(--text); box-shadow: 0 0 0 3px rgba(20,184,166,.15); }
+.chip.new { outline: 2px solid var(--brand-2); box-shadow: 0 0 0 3px rgba(20,184,166,.15); }
 /* .chip.new::after { content: 'NEW'; margin-left: 6px; font-size: .68rem; background: var(--text); color: #07221f; padding: 3px 6px; border-radius: 999px; font-weight: 900; letter-spacing:.4px; } */
 
 /* Table layout */
@@ -108,11 +114,11 @@ td.thumb-cell { width: 60px; }
 .mini-thumb { width: 40px; height: 40px; border-radius: 8px; overflow: hidden; border:1px solid var(--border); background:#f3f4f6; display:grid; place-items:center; }
 .mini-thumb img { width: 100%; height: 100%; object-fit: cover; }
 .mini-thumb .fallback { color:#64748b; font-weight:700; font-size:.8rem; }
-.badge { display:inline-block; padding: 4px 8px; border-radius:999px; background:#eef2ff; color:#1e3a8a; font-size:.8rem; border:1px solid #e5e7eb; }
+.badge { display:inline-block; padding: 4px 8px; border-radius:999px; background:#eef2ff; color:#01445E; font-size:.8rem; border:1px solid #e5e7eb; }
 
 /* Detail rows for table */
 .detail-row td { background:#ffffff; }
-.detail-content { padding: 10px 6px; border-left: 4px solid var(--text); border-radius: 8px; background:#f8fafc; }
+.detail-content { padding: 10px 6px; border-left: 4px solid var(--brand-2); border-radius: 8px; background:#f8fafc; }
 .detail-content .section-title { display:flex; align-items:center; gap:10px; margin: 6px 0 8px; color: var(--muted); }
 .summary {
     display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 10px;
@@ -216,13 +222,33 @@ td.thumb-cell { width: 60px; }
   font-size: 0.9em;
   color: #666;
 }
+
+/* Make attribute groups display in two columns */
+.attribute-selectors {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr); /* Two equal columns */
+    gap: 10px; /* Space between columns */
+    max-width: 800px; /* Optional: Limit max width */
+}
+
+/* Make section headers span full width */
+.mandatory-title,
+.optional-title {
+    grid-column: span 2; /* Make them stretch across both columns */
+}
+
+/* Make each attribute group fill one column */
+.attribute-chip-group {
+    width: 100%; /* Ensure full use of column space */
+}
+
 /* 
 /* General layout */
 .attribute-selectors {
     /* font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; */
     max-width: none;
     margin: 5px auto;
-    padding: 15px;
+    padding: 10px;
     background-color: #fff;
     border: 1px solid #ddd;
     border-radius: 10px;
@@ -236,7 +262,7 @@ td.thumb-cell { width: 60px; }
     font-size: 1.2em;
     font-weight: 600;
     color: #333;
-    margin-bottom: 10px;
+    margin-bottom: 0px;
     padding-left: 5px;
     border-left: 4px solid #007BFF;
 }
@@ -268,7 +294,7 @@ td.thumb-cell { width: 60px; }
     background-color: #f0f4f8;
     border: 1px solid #ccc;
     border-radius: 20px;
-    padding: 8px 14px;
+    padding: 2px 10px;
     cursor: pointer;
     transition: background-color 0.2s, box-shadow 0.2s;
     user-select: none;
@@ -339,4 +365,41 @@ td.thumb-cell { width: 60px; }
         justify-self: end;
         margin-top: 8px;
     }
+}
+
+/* @media (max-width: 600px) {
+    .attribute-selectors {
+        grid-template-columns: 1fr; 
+    }
+
+    .chips-container {
+        flex-direction: column;
+        gap: 8px;
+    }
+
+    .attribute-chip {
+        width: 100%;
+        justify-content: center;
+    }
+} */
+
+td{
+    font-size: 12px;
+}
+
+.toolbar {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    flex-wrap: wrap;
+}
+
+#thresholdRange {
+    width: 150px;
+}
+
+/* Optional: style tweaks */
+#mandatory-attributes {
+    min-width: 200px;
+    width: auto !important; /* Override inline style */
 }

+ 115 - 15
content_quality_tool_public/static/js/attr-extraction.js

@@ -1,11 +1,16 @@
 jQuery.noConflict(); // Release $ to other libraries
-console.log(typeof jQuery);
+// console.log(typeof jQuery);
 // $ = jQuery;
 
 // --- Config ---
 const UPLOAD_API_URL = '/attr/products/upload-excel/'; // TODO: set to your upload endpoint
 const ACCEPT_TYPES = '*'; // e.g., 'image/*,.csv,.xlsx'
 
+
+const thresholdInput = document.getElementById('thresholdRange');
+const thresholdValueDisplay = document.getElementById('thresholdValue');
+
+
 var PRODUCT_BASE = [
     // { id: 1, item_id: 'SKU001', product_name: "Levi's Jeans", product_long_description: 'Classic blue denim jeans with straight fit.', product_short_description: 'Blue denim jeans.', product_type: 'Clothing', image_path: 'media/products/jeans.jpg', image: 'http://127.0.0.1:8000/media/products/jeans.png' },
     // { id: 2, item_id: 'SKU002', product_name: 'Adidas Running Shoes', product_long_description: 'Lightweight running shoes with breathable mesh and cushioned sole.', product_short_description: "Men's running shoes.", product_type: 'Footwear', image_path: 'media/products/shoes.png', image: 'http://127.0.0.1:8000/media/products/shoes.png' },
@@ -26,15 +31,16 @@ document.addEventListener('DOMContentLoaded', () => {
     })
     .then(response => response.json())
     .then(data => {
-        console.log("data",data);
+        // console.log("data",data);
         // --- Wire up ---
         PRODUCT_BASE = data;
         PRODUCT_BASE = PRODUCT_BASE.map((d)=>{return {...d,mandatoryAttributes:["color","size"]}});
-        console.log("PRODUCT_BASE",PRODUCT_BASE);
+        // console.log("PRODUCT_BASE",PRODUCT_BASE);
         if(PRODUCT_BASE.length > 0){
             $('#paginationBar').style.display = 'block';
         }
         renderProducts();
+        getAtributeList();
         document.getElementById('btnSubmit').addEventListener('click', submitAttributes);
         document.getElementById('btnReset').addEventListener('click', resetAll);
         // document.getElementById('btnSelectAll').addEventListener('click', () => {
@@ -118,7 +124,8 @@ function renderChips(container, obj, memoryMap) {
     Object.entries(obj || {}).forEach(([k, v]) => {
     const chip = el('span', 'chip');
     const kEl = el('span', 'k'); kEl.textContent = k + ':';
-    const vEl = el('span', 'v'); vEl.textContent = ' ' + String(v);
+    // console.log("v",v);
+    const vEl = el('span', 'v'); vEl.textContent = ' ' + String(v[0].value)  +' (' +String(v[0].source) + ')';
     chip.appendChild(kEl); chip.appendChild(vEl);
     const was = memoryMap.get(k);
     if (was === undefined || was !== v) chip.classList.add('new');
@@ -141,7 +148,7 @@ function createProductCard(p) {
     const left = el('div', 'thumb');
     const img = new Image(); img.src = mediaUrl+p.image_path || p.image || '';
      img.alt = `${p.product_name} image`;
-    console.log("img",img);
+    // console.log("img",img);
     // img.onerror = () => { img.remove(); const fb = el('div', 'fallback'); fb.textContent = (p.product_name || 'Product').split(' ').map(w => w[0]).slice(0,2).join('').toUpperCase(); left.appendChild(fb); };
     img.onerror = () => { img.src = mediaUrl+"media/images/no-product.png" };
     left.appendChild(img);
@@ -308,7 +315,7 @@ function renderProductsCards(items = getCurrentSlice()) {
 function createMiniThumb(p) {
     const mt = el('div', 'mini-thumb');
     const img = new Image(); img.src = mediaUrl+p.image_path || p.image || ''; img.alt = `${p.product_name} image`;
-    console.log("img",img);
+    // console.log("img",img);
     img.onerror = () => { img.src = mediaUrl+"media/images/no-product.png" };
     // img.onerror = () => { img.remove(); const fb = el('div', 'fallback'); fb.textContent = (p.product_name || 'Product').split(' ').map(w => w[0]).slice(0,2).join('').toUpperCase(); mt.appendChild(fb); };
     mt.appendChild(img);
@@ -503,7 +510,7 @@ function renderProductsTable(items = getCurrentSlice()) {
     const wrap = document.getElementById('tableContainer');
     wrap.innerHTML = '';
     const table = document.createElement('table');
-    table.classList.add('table', 'table-striped', 'table-bordered');
+    table.classList.add('table', 'table-striped', 'table-bordered','table-responsive');
     
     const thead = document.createElement('thead'); 
     const trh = document.createElement('tr');
@@ -720,6 +727,7 @@ function renderInlineForTable() {
     $('#statTotal').textContent = api.total_products ?? 0;
     $('#statOk').textContent = api.successful ?? 0;
     $('#statKo').textContent = api.failed ?? 0;
+    $('#api-summary').style.display = 'block';
 }
 
 
@@ -732,7 +740,7 @@ function renderProducts() {
     if (layoutMode === 'cards') {
     $('#cardsContainer').style.display = '';
     $('#tableContainer').style.display = 'none';
-    console.log("PRODUCT_BASE",PRODUCT_BASE);
+    // console.log("PRODUCT_BASE",PRODUCT_BASE);
     renderProductsCards();
     } else {
     $('#cardsContainer').style.display = 'none';
@@ -754,13 +762,23 @@ function submitAttributes() {
         return; 
     } 
     // if (selectedIds.size === 0) { alert('Please select at least one product.'); return; }
-    console.log("selectedIds",selectedIds);
+    // console.log("selectedIds",selectedIds);
     jQuery('#full-page-loader').show();  
     // let inputArray = {
     //       "product_ids" : [...selectedIds]
     //     }
     const extractAdditional = document.getElementById('extract_additional').checked;
     const processImage = document.getElementById('process_image').checked;
+    // const selectedMultiples = document.getElementById('#mandatory-attributes');
+    // const selectedValues = Array.from(selectedMultiples.selectedOptions).map(option => option.value);
+    const selectElement = document.getElementById('mandatory-attributes');
+
+    const selectedValues = Array.from(selectElement.selectedOptions).map(option => option.value);
+
+    // console.log(selectedValues); // Logs an array of selected values
+    // console.log("thresholdValueDisplay",thresholdValueDisplay.value);
+    const threshold = parseFloat(document.getElementById('thresholdRange').value);
+
 
     // Transform the new state array into the required API format
     const itemIds = selectedProductsWithAttributes.map(p => p.item_id);
@@ -785,10 +803,14 @@ function submitAttributes() {
 
     let inputArray = {
               "products": payloadForQ1,
-              
               "model": "llama-3.1-8b-instant",
               "extract_additional": extractAdditional,
-              "process_image": processImage
+              "process_image": processImage,
+              "multiple": selectedValues,
+              "threshold_abs": threshold,  // Lower threshold to be more permissive
+            //   "margin": 0.3,  // Larger margin to include more candidates
+            //   "use_adaptive_margin": true,
+            //   "use_semantic_clustering": true
             }
     let raw = JSON.stringify(inputArray);
      fetch('/attr/batch-extract/', {
@@ -801,7 +823,7 @@ function submitAttributes() {
     })
     .then(response => response.json())
     .then(data => {
-        console.log("response data",data); 
+        // console.log("response data",data); 
         FAKE_API_RESPONSE = data;   
         renderInlineAttributes();
         jQuery('#full-page-loader').hide();  
@@ -818,6 +840,18 @@ function resetAll() {
     document.getElementById('statOk').textContent = '0';
     document.getElementById('statKo').textContent = '0';
     $('#api-summary').style.display = 'none';
+
+    // ✅ Clear Select2 selections
+    jQuery('#mandatory-attributes').val(null).trigger('change');
+
+    // ✅ Reset threshold input (and display)
+    const thresholdInput = document.getElementById('thresholdRange');
+    const thresholdDisplay = document.getElementById('thresholdValue');
+
+    thresholdInput.value = '0.2'; // or any default value you prefer
+    if (thresholdDisplay) {
+        thresholdDisplay.textContent = '0.2';
+    }
 }
 
 function setLayout(mode) {
@@ -1039,6 +1073,72 @@ function isAttributeValueSelected(itemId, attrName, value) {
 
 
 
-$('.attribute-select').select2({
-    placeholder: 'Select product attributes'
-});
+// $('.attribute-select').select2({
+//     placeholder: 'Select product attributes'
+// });
+
+function getAtributeList(){
+        jQuery('#full-page-loader').show();  
+            try{
+            fetch('/attr/products/attributes', {
+                method: 'GET', // or 'POST' if your API expects POST
+                headers: {
+                    'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || ''
+                }
+            })
+            .then(response => response.json())
+            .then(data => {
+                // console.log("data",data);
+                let attributesData = data;
+                 // Step 1: Extract unique mandatory attribute names
+                const mandatoryAttributes = [...new Set(
+                    attributesData
+                        .filter(attr => attr.is_mandatory === "Yes")
+                        .map(attr => attr.attribute_name)
+                )];
+
+                // Step 2: Populate the select element
+                const $select = jQuery('#mandatory-attributes');
+                $select.append(new Option("Select All", "select_all")); // Add "Select All" option first
+
+                mandatoryAttributes.forEach(attr => {
+                    $select.append(new Option(attr, attr));
+                });
+
+                // Step 3: Initialize Select2 with placeholder
+                // $select.select2({
+                //     placeholder: "Select mandatory attributes",
+                //     allowClear: true
+                // });
+
+                // Step 4: Handle 'Select All' logic
+                $select.on('select2:select', function (e) {
+                    if (e.params.data.id === "select_all") {
+                        // Select all real options except "Select All"
+                        const allOptions = mandatoryAttributes;
+                        $select.val(allOptions).trigger('change');
+                    }
+                });
+
+                jQuery('#full-page-loader').hide();
+            });
+        }catch(err){
+            console.log("err",err);
+            jQuery('#full-page-loader').hide();
+
+        }
+    
+}
+document.addEventListener("DOMContentLoaded", function () {
+    // Update span when range changes
+    thresholdInput.addEventListener('input', function () {
+        // console.log("this.value",this.value);
+        thresholdValueDisplay.textContent = this.value;
+    });
+});
+
+// Get threshold value when needed
+function getThreshold() {
+    // console.log("parseFloat(thresholdInput.value)",parseFloat(thresholdInput.value));
+    return parseFloat(thresholdInput.value);
+}

+ 24 - 6
content_quality_tool_public/templates/attr-extraction.html

@@ -85,12 +85,12 @@
                         </div>
                         <!-- <input type="checkbox" id="extract_additional" name="extract_additional"/>
                         <input type="checkbox" id="process_image" name="process_image"/> -->
-                        <button id="btnSubmit" class="btn btn-success">Submit</button>
+                        <button id="btnSubmit" class="btn btn-primary">Submit</button>
                         <button id="btnReset" class="btn btn-secondary">Reset</button>
-                        <button id="btnSelectAll" class="btn btn-ghost">Select all</button>
+                        <button id="btnSelectAll" class="btn btn-primary">Select all</button>
                         <!-- Upload trigger button -->
                         <button id="btnUpload" type="button"
-                                class="btn btn-primary"
+                                class="btn btn-info"
                                 data-bs-toggle="modal"
                                 data-bs-target="#uploadModal">
                                 Upload
@@ -117,16 +117,24 @@
                         <div class="toolbar">
                             <span class="pill" id="selectionInfo">No products selected</span>
                             <div class="seg" role="tablist" aria-label="Layout toggle">
-                            <button id="btnCards" class="active" role="tab" aria-selected="true">Cards</button>
-                            <button id="btnTable" role="tab" aria-selected="false">Table</button>
+                                <button id="btnCards" class="active" role="tab" aria-selected="true">Cards</button>
+                                <button id="btnTable" role="tab" aria-selected="false">Table</button>
                             </div>
+                            
+                        </div>
+                        <div><p>Select multiple :<select id="mandatory-attributes" name="mandatoryAttributes[]" aria-labelledby="select a attribute for which multiple data required" multiple="multiple" style="width: 100%;"></select></p></div>
+                        <div>
+                            <label for="thresholdRange">Threshold:</label>
+                            <input type="range" id="thresholdRange" min="0" max="1" step="0.01" value="0.2">
+                            <span id="thresholdValue">0.2</span>
                         </div>
                         </div>
                         <div class="card-body">
                         <!-- Cards layout container -->
                         <div id="cardsContainer" class="list" aria-live="polite"></div>
                         <!-- Table layout container -->
-                        <div id="tableContainer" class="table-wrap" style="display:none" aria-live="polite"></div>
+                         <!-- table-wrap  -->
+                        <div id="tableContainer" class="table-responsive" style="display:none" aria-live="polite"></div>
                         <!-- Pagination bar -->
                         <div id="paginationBar" class="pagination-bar" aria-label="Pagination" style="display: none;"></div>
                         </div>
@@ -228,6 +236,16 @@
                 });
             }
         });
+        $('#mandatory-attributes').select2({
+            placeholder: 'Select mandatory attributes',
+            allowClear: true,
+            width: '200px' // or 'resolve' if dynamically sized
+        });
+
+        // In your Javascript (external .js resource or <script> tag)
+        // $(document).ready(function() {
+        //     // jQuery('#mandatory-attributes').select2();
+        // });
     </script> 
     <script src="{% static 'js/attr-extraction.js' %}"></script>