diff --git a/site/assets/img/events-people.png b/site/assets/img/events-people.png
new file mode 100644
index 0000000000..8a66643dca
Binary files /dev/null and b/site/assets/img/events-people.png differ
diff --git a/site/assets/img/standard-hero-dots-2-1024x620.png b/site/assets/img/standard-hero-dots-2-1024x620.png
new file mode 100644
index 0000000000..cfae6163a3
Binary files /dev/null and b/site/assets/img/standard-hero-dots-2-1024x620.png differ
diff --git a/site/index.qmd b/site/index.qmd
index 5f022247ad..019ce21a54 100644
--- a/site/index.qmd
+++ b/site/index.qmd
@@ -3,6 +3,7 @@ pagetitle: "Welcome to our documentation"
page-layout: full
sidebar: false
repo-actions: false
+format: html
resources:
- assets/**
filters:
@@ -70,21 +71,65 @@ a:hover {
background-color: #F9F9F9;
}
-#searchbox {
+.search-wrapper {
+ position: relative;
+ width: 100%;
max-width: 600px;
+ margin: 0;
+ padding: 0;
+}
+
+.input-container {
+ position: relative;
+ width: 100%;
+ max-width: 400px;
+}
+
+#searchbox {
+ width: 100%;
padding: 10px;
+ padding-left: 40px; /* Add padding so the text doesn’t overlap the icon */
border-radius: 8px;
- width: 100%;
box-sizing: border-box;
}
+.search-icon {
+ position: absolute;
+ left: 10px;
+ top: 50%;
+ transform: translateY(-50%);
+ pointer-events: none;
+ fill: #4a4a4a;
+}
+
+#toast {
+ position: absolute;
+ top: calc(100% + 5px);
+ left: 0;
+ width: 100%;
+ color: white;
+ padding-left: 10px;
+ text-align: left;
+ z-index: 1000;
+ font-weight: bold;
+ opacity: 0;
+ transition: opacity 0.5s ease;
+ background-color: transparent;
+ box-shadow: none;
+ border: none;
+}
+
+#toast.show {
+ opacity: 1;
+}
+
#hits {
display: none;
overflow-y: auto;
- width: 100vw;
- max-width: 800px;
+ width: 100%;
+ max-width: 600px;
max-height: 400px;
- margin: 20px auto;
+ margin-top: 50px;
padding: 15px;
background-color: #f9f9f9;
border: 1px solid #ccc;
@@ -99,7 +144,7 @@ a:hover {
}
#hits div:hover {
- background-color: #EAF8FA; /* Light green background for the entire search result on hover */
+ background-color: #EAF8FA;
}
#hits a strong {
@@ -112,6 +157,23 @@ a:hover {
text-decoration: none;
}
+#explain {
+ display: none;
+ position: relative;
+ z-index: 100;
+ overflow-y: auto;
+ width: 100%;
+ max-width: 600px;
+ max-height: 1000px;
+ margin: 20px auto;
+ padding: 15px;
+ color: #222425;
+ background-color: #f9f9f9;
+ border: 1px solid #ccc;
+ border-radius: 8px;
+ box-sizing: border-box;
+}
+
```
@@ -135,24 +197,33 @@ a:hover {
The **purpose-built platform** for model risk management teams to test, document, validate, and govern Generative AI, AI, and statistical models with speed and confidence.
::::
-[ask a question]{.smallcaps}
-
-
-
+[How do I ... ?]{.smallcaps}
+
+
+
+
+
Press Enter for more context
+
-
+
quickstart open-source software
-
:::
::: {.w-40-ns}
+
+
::: {.image-container}


@@ -161,6 +232,7 @@ The **purpose-built platform** for model risk management teams to test, document

:::
+
:::
::: {.w-10-ns}
@@ -181,7 +253,6 @@ The **purpose-built platform** for model risk management teams to test, document
:::
-
::: {.w-25-ns .mb4}
:::: {.ba .b--black-10 .br3 .shadow-4 .bg-white .pt4 .pb3 .h-100 .grow}
@@ -203,7 +274,6 @@ The **purpose-built platform** for model risk management teams to test, document
::::
-
::::
:::
@@ -227,7 +297,6 @@ The **purpose-built platform** for model risk management teams to test, document
developer reference
-
::::
::::
@@ -259,7 +328,6 @@ The **purpose-built platform** for model risk management teams to test, document
:::
-
::: {.w-10-ns}
:::
diff --git a/site/scripts/validsearch.js b/site/scripts/validsearch.js
index 97bffc22a2..9ab1a417c7 100644
--- a/site/scripts/validsearch.js
+++ b/site/scripts/validsearch.js
@@ -2,9 +2,12 @@
// See the LICENSE file in the root of this repository for details.
// SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial
-// Fetch the generated Algolia search index
+// Fetch the local Algolia search index
const SEARCH_INDEX_URL = 'search.json';
+// Set to 'true' to disable cleaning up the streaming text (editing is done on the backend)
+let disableCleanText = true;
+
// Function to load search.json file and return it as a JavaScript object
async function loadSearchIndex() {
const response = await fetch(SEARCH_INDEX_URL);
@@ -17,52 +20,154 @@ async function setupLunr() {
const searchData = await loadSearchIndex();
// Create Lunr.js index
- const idx = lunr(function () {
- // Define the fields to index
+ const searchIndex = lunr(function () {
this.ref('href');
- this.field('title');
- this.field('text');
- this.field('section');
- this.field('crumbs'); // Make sure crumbs is also indexed
+ this.field('title', { boost: 10 });
+ this.field('text', { boost: 5 });
+ this.field('section', { boost: 0.5 });
+ this.field('crumbs', { boost: 0.2 });
- // Index crumbs as a space-separated string
searchData.forEach(function (doc) {
const modifiedDoc = {
...doc,
- crumbs: Array.isArray(doc.crumbs) ? doc.crumbs.join(' ') : doc.crumbs // Convert crumbs array to string
+ crumbs: Array.isArray(doc.crumbs) ? doc.crumbs.join(' ') : doc.crumbs
};
- this.add(modifiedDoc); // Add the modified document with stringified crumbs
+ this.add(modifiedDoc); // Add the modified document
}, this);
});
- return { idx, searchData };
+ return { searchIndex, searchData };
+}
+
+// Strip out text that can cause poor search ranking
+function sanitizeInput(input) {
+ let sanitized = input.toLowerCase(); // Convert to lowercase for consistent matching
+ sanitized = sanitized.replace(/^how do i\s+/, ''); // Remove common phrases like "how do I"
+
+ sanitized = sanitized.replace(/[?.!]/g, ''); // Remove punctuation marks like ? or !
+ sanitized = sanitized.trim(); // Trim any leading or trailing spaces
+
+ return sanitized;
+}
+
+// Clean up response text for minor tweaks, disabled by default (and major changes should be done on the backend)
+function cleanText(text) {
+ return text
+ .replace(/\s+([,.;!?()])/g, '$1') // Remove space before punctuation
+ .replace(/\(\s+/g, '(') // Remove space after opening parenthesis
+ .replace(/\s+\)/g, ')') // Remove space before closing parenthesis
+ .replace(/\s+-\s+/g, '-') // Remove spaces around hyphens
+ .replace(/\s+/g, ' ') // Replace multiple spaces with a single space
+ .trim(); // Trim any extra spaces
+}
+
+
+// Prompt the user to hit Enter for more info
+function showToast() {
+ const toast = document.getElementById('toast');
+ const searchbox = document.getElementById('searchbox');
+
+ // Listen to input events on the searchbox
+ searchbox.addEventListener('input', function () {
+ if (searchbox.value.trim() !== '') {
+ toast.classList.add('show');
+ } else {
+ toast.classList.remove('show');
+ }
+ });
+}
+
+// Function to fetch and render streaming explanation
+async function fetchExplainResults(query) {
+ const explainUrl = 'http://localhost:3333/explain-results';
+ const explainContainer = document.getElementById('explain');
+
+ let buffer = ''; // Buffer to accumulate incoming HTML chunks
+
+ try {
+ const response = await fetch(explainUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ userQuery: query })
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! Status: ${response.status}`);
+ }
+
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder("utf-8");
+
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+
+ let chunk = decoder.decode(value, { stream: true });
+
+ // Remove "data: " and "data: [DONE]"
+ chunk = chunk.replace(/data: /g, '').replace(/\[DONE\]/g, '');
+
+ // Add the cleaned chunk to the buffer
+ buffer += chunk;
+
+ // Append the cleaned HTML directly to the explainContainer
+ explainContainer.innerHTML += buffer;
+
+ // Clear the buffer after appending
+ buffer = '';
+ }
+
+ } catch (error) {
+ console.error("Error fetching explanation:", error);
+ }
}
// Clear the search input when the page loads
window.onload = function() {
- document.getElementById('searchbox').value = ''; // Clear the search box input
+ document.getElementById('searchbox').value = '';
+ showToast();
};
-setupLunr().then(({ idx, searchData }) => {
- document.getElementById('searchbox').addEventListener('input', function (event) {
- const query = event.target.value.trim();
+// Function to handle the search and display results
+setupLunr().then(({ searchIndex, searchData }) => {
+ document.getElementById('searchbox').addEventListener('input', async function (event) {
+ let query = event.target.value.trim();
+
+ // Sanitize the query
+ query = sanitizeInput(query); // Call the sanitizeInput function here
+
const hitsContainer = document.getElementById('hits');
- hitsContainer.innerHTML = ''; // Clear previous results
+ const explainContainer = document.getElementById('explain');
+
+ hitsContainer.innerHTML = '';
- // Hide the hits container if the search box is empty
+ // Clear the explain div when the input is empty
if (query === '') {
hitsContainer.style.display = 'none';
+ explainContainer.innerHTML = '';
+ explainContainer.style.display = 'none';
return;
}
- const exactResults = idx.search(`"${query}"`);
- let results = exactResults.length > 0 ? exactResults : idx.search(query);
+ // Perform an exact match search first
+ let lunrResults = searchIndex.search(`"${query}"`);
+
+ // If no exact matches found, fall back to a regular search
+ if (lunrResults.length === 0) {
+ lunrResults = searchIndex.search(query);
+ }
+
+ // Ensure that you limit the results to the top 20 in both cases
+ lunrResults = lunrResults.slice(0, 20);
+
+ if (lunrResults.length > 0) {
+ hitsContainer.style.display = 'block';
- // Show the hits container if there are results, otherwise hide it
- if (results.length > 0) {
- hitsContainer.style.display = 'block'; // Show the container
- results.forEach((result) => {
+ lunrResults.forEach((result) => {
const doc = searchData.find(d => d.href === result.ref);
+
if (doc) {
const resultElement = document.createElement('div');
const crumbs = Array.isArray(doc.crumbs) ? doc.crumbs.join(' > ') : doc.crumbs;
@@ -71,17 +176,32 @@ setupLunr().then(({ idx, searchData }) => {
${doc.title}
${doc.section}
- ${doc.text.substring(0, 100)}...
-
- ${crumbs}
-
+ ${crumbs}
+ ${doc.text.substring(0, 100)}...
`;
hitsContainer.appendChild(resultElement);
}
});
} else {
- hitsContainer.style.display = 'none'; // Hide the container if no results
+ hitsContainer.style.display = 'none';
+ }
+ });
+
+ // Add event listener for hitting Enter to start explanation
+ document.getElementById('searchbox').addEventListener('keydown', async function (event) {
+ if (event.key === 'Enter') {
+ let query = document.getElementById('searchbox').value.trim();
+
+ // Sanitize the query before fetching the explanation
+ query = sanitizeInput(query);
+
+ const explainContainer = document.getElementById('explain');
+
+ if (query) {
+ explainContainer.style.display = 'block';
+ await fetchExplainResults(query);
+ }
}
});
-});
\ No newline at end of file
+});
diff --git a/site/styles.css b/site/styles.css
index e85069a100..bef9cdfb16 100644
--- a/site/styles.css
+++ b/site/styles.css
@@ -318,7 +318,6 @@ input[type="checkbox"][checked] {
transform: scale(1.05);
}
-
.image-container {
position: relative;
z-index: -1;
@@ -332,7 +331,6 @@ input[type="checkbox"][checked] {
animation: fadeUp 25s infinite;
}
-
.image-container img:nth-child(1) {
animation-delay: 0s;
}