Skip to main content

Visual Discovery Sample

The Fast Simon SDK empowers developers to implement advanced visual product discovery with minimal effort. Key features include:

  • visualSearch: Allows users to search for products by uploading an image, identifying visually similar items.
  • discoveryImages: Fetches a gallery of trending or curated images to inspire purchases.
  • productsByDiscovery: Retrieves related or recommended products based on specified discovery modes.

SDK Initialization

The Fast Simon SDK is initialized using the configuration provided in the window.__fast_simon_sdk_config object in main.html.

// Required Parameters
window.__fast_simon_sdk_config = {
store_uuid: "YOUR_STORE_UUID", // Unique identifier for your store
store_id: YOUR_STORE_ID, // Store ID as provided by Fast Simon
type: "SPA", // Specify Single-Page App (SPA) or Multi-Page App (MPA)
onReady: () => {
console.log("Fast Simon SDK is ready!");
}
};

Note: Replace YOUR_STORE_UUID and YOUR_STORE_ID with the credentials provided by Fast Simon. Ensure the type field aligns with your application architecture (e.g., SPA for Single-Page Applications, MPA for Multi-Page Applications).

Application Flow

Add a visual discovery icon to your search bar to open a modal displaying a gallery of images with an option to upload a new image for visual discovery.

<div class="search-bar">
<label id="upload-modal-trigger" class="upload-icon">
<img src="/visual_discovery_camera_black.svg" alt="Open Visual Discovery Modal" />
</label>
<input type="text" id="search-input" placeholder="Search for products by keyword..." />
<button id="search-button">
<img src="assets/search-icon.svg" alt="Search" />
</button>
</div>
// Open the Visual Search Modal on Icon Click
document.getElementById("upload-modal-trigger").addEventListener("click", () => {
openVisualSearchModal();
});

Use the discoveryImages method to fetch and display a gallery of trending or curated images.

function discoveryImages() {
window.FastSimonSDK.discoveryImages({
mode: "popular", // Specify the mode (e.g., popular, trending)
callback: ({ action, payload }) => {
if (action === "DiscoveryImages") {
updateGallery(payload); // Update the gallery with fetched images
}
},
});
}
function updateGallery(gallery) {
const modalLeftSection = document.getElementById("modal-left-section");
modalLeftSection.style.display = 'none';
renderList("Gallery", gallery, true);
}
function renderList(title, items, isGallery) {
const modalBody = document.querySelector(".modal-body");
const modalLeft = document.getElementById("modal-left-section");
const resultsSection = document.getElementById("results-section");
const backButton = document.getElementById("back-to-gallery");

const hasT2 = items.length && items[0]?.t2; // Check for `t2` key in items

// Update the HTML content
resultsSection.innerHTML = `
<h3 id="results-title">${title}</h3>
<div id="product-list">
${items.length
? items.map(createProductCard).join("")
: "<p>No products found.</p>"
}
</div>
`;

// Adjust visibility and styles
if (isGallery) modalBody.classList.add("gallery-active");
modalLeft.style.display = hasT2 ? "block" : "none";
modalLeft.classList.toggle("hidden", !hasT2);
resultsSection.classList.toggle("full-width", !hasT2);
backButton.classList.toggle("hidden", !hasT2);

// Attach events to items
hasT2 ? attachProductsEvents() : attachCardEvents();
}

function createProductCard({ image_id, l, p, t, id, u }) {
return `
<div class="product" data-image-id="${image_id || id}" data-product-url="${image_id ? '' : u}">
${t ? `<img src="${t}" alt="${l || 'No Image'}" />` : ""}
${!image_id ? `<h3>${l}</h3><p>${p}</p>` : ""}
</div>
`;
}

3. Handling Image Uploads

Inside the modal, provide a button for users to upload an image for visual discovery.

<div class="modal-upload-header-container-wrap">
<h2>Search by Photo</h2>
<p>Drag and drop a photo or</p>
<div class="upload-button-wrapper">
<label for="image-upload" class="modern-upload-button">
<span>Upload Image</span>
<input type="file" id="image-upload" class="hidden" accept="image/*" hidden />
</label>
</div>
</div>

Handling Image Uploads with visualSearch

document.getElementById("image-upload").addEventListener("change", async (event) => {
const file = event.target.files[0];
if (!file) return alert("Please upload an image.");

window.FastSimonSDK.visualSearch({
fileData: file,
callback: ({ action, payload }) => {
if (payload) {
// Update the preview with bounding boxes
updateUploadedImage(payload.imageBase64, { file, boxes: payload.bbox });

// Render search results or an empty state
const items = action === "ImageSearch" ? payload.items : [];
renderList("Search Results", items, false);
}
},
});
});

4. Handling Bounding Boxes

If the SDK detects bounding boxes in the uploaded image, use these to create interactive dots and refine searches.

function updateUploadedImage(imageData, additionalValue) {
const imagePreview = document.getElementById("modal-image-preview");
imagePreview.innerHTML = `
<img
src="${imageData.url || imageData}"
alt="Uploaded Image"
style="max-width: 243px; position: relative;"
/>
`;

additionalValue?.boxes?.forEach((box) => {
const dot = createDot(box.dot);
const boundingBox = createBoundingBox(box.position);

dot.addEventListener("click", () => {
boundingBox.style.display = boundingBox.style.display === "none" ? "block" : "none";
triggerFastSimonSDK(additionalValue.file, box.name);
});

imagePreview.append(dot, boundingBox);
});
}

Create a dot icon on the uploaded

function createDot({ x, y }) {
const dot = document.createElement("div");
dot.classList.add("dot");
dot.style.cssText = `
position: absolute;
left: ${x}%;
top: ${y}%;
transform: translate(-50%, -50%);
cursor: pointer;
`;
dot.innerHTML = SVG_MARKUP; // Replace with your SVG markup
return dot;
}

Show the bounding box when clicking on the Dot

function createBoundingBox({ left, top, width, height }) {
const boundingBox = document.createElement("div");
boundingBox.classList.add("bounding-box");
boundingBox.style.cssText = `
position: absolute;
left: ${left}%;
top: ${top}%;
width: ${width}%;
height: ${height}%;
border: 2px hidden #00feff;
border-radius: 5px;
box-shadow: 0 0 0 2000px rgba(0, 0, 0, 0.5);
display: none;
`;
return boundingBox;
}

Resources

For more details and additional examples, visit: