From 3958654925e92b1046b3ed5d49160514b6e48258 Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Tue, 6 Jun 2023 14:36:21 -0400
Subject: added rec stuff and begun updating components

---
 .../views/newlightbox/Header/LightboxHeader.tsx    | 67 ++++++++++++++++++++++
 1 file changed, 67 insertions(+)
 create mode 100644 src/client/views/newlightbox/Header/LightboxHeader.tsx

(limited to 'src/client/views/newlightbox/Header/LightboxHeader.tsx')

diff --git a/src/client/views/newlightbox/Header/LightboxHeader.tsx b/src/client/views/newlightbox/Header/LightboxHeader.tsx
new file mode 100644
index 000000000..a542d2943
--- /dev/null
+++ b/src/client/views/newlightbox/Header/LightboxHeader.tsx
@@ -0,0 +1,67 @@
+import './LightboxHeader.scss';
+import * as React from 'react';
+import { INewLightboxHeader } from "./utils";
+import { NewLightboxView } from '../NewLightboxView';
+import { StrCast } from '../../../../fields/Types';
+import { EditableText } from '../components/EditableText';
+import { getType } from '../utils';
+import { Button, Size, Type } from 'browndash-components';
+import { MdExplore, MdTravelExplore } from 'react-icons/md'
+import { BsBookmark, BsBookmarkFill } from 'react-icons/bs'
+import { Doc } from '../../../../fields/Doc';
+
+
+export const NewLightboxHeader = (props: INewLightboxHeader) => {
+    const {height = 100, width} = props;
+    const [doc, setDoc] = React.useState<Doc | undefined>(NewLightboxView.LightboxDoc)
+    const [editing, setEditing] = React.useState<boolean>(false)
+    const [title, setTitle] = React.useState<JSX.Element | null>(
+        (null)
+    )
+    React.useEffect(() => {
+        let lbDoc = NewLightboxView.LightboxDoc
+        setDoc(lbDoc)
+        if (lbDoc) {
+            setTitle(
+                <EditableText 
+                editing={editing}
+                text={StrCast(lbDoc.title)} 
+                onEdit={(newText: string) => {
+                    if(lbDoc) lbDoc.title = newText;
+                }} 
+                setEditing={setEditing}            
+            />)
+        }
+    }, [NewLightboxView.LightboxDoc])
+
+    const [saved, setSaved] = React.useState<boolean>(false)
+
+    if (!doc) return null
+    else return <div className={`newLightboxHeader-container`} onPointerDown={(e) => e.stopPropagation()} style={{ minHeight: height, height: height, width: width }}>
+        <div className={`title-container`}>
+            <div className={`lb-label`}>Title</div>
+            {title}
+        </div>
+        <div className={`type-container`}>
+            <div className={`lb-label`}>Type</div>
+            <div className={`type`}>{getType(StrCast(doc.type))}</div>
+        </div>
+        <div style={{gridColumn: 2, gridRow: 1, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center'}}>
+            <div className={`lb-button2`} onClick={() => setSaved(!saved)}>
+                {saved ? <BsBookmarkFill/> : <BsBookmark/>}
+            </div>
+            <div className={`lb-button2`} onClick={() => setSaved(!saved)}>
+                {saved ? <BsBookmarkFill/> : <BsBookmark/>}
+            </div>
+        </div>
+        <div  style={{gridColumn: 2, gridRow: 2, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center'}}>
+            <div className={`lb-button ${NewLightboxView.ExploreMode}`} onClick={() => {
+                console.log(NewLightboxView.ExploreMode)
+                NewLightboxView.SetExploreMode(!NewLightboxView.ExploreMode)
+            }}>
+                <MdTravelExplore/>
+                t-SNE 2D Embeddings
+            </div>
+        </div>
+    </div>
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From abd79b36f15466db05a2c1f379260b85e4ae838b Mon Sep 17 00:00:00 2001
From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com>
Date: Wed, 7 Jun 2023 11:50:45 -0400
Subject: added doc recommendations to search

---
 .../views/newlightbox/Header/LightboxHeader.scss   | 21 +------
 .../views/newlightbox/Header/LightboxHeader.tsx    | 25 ++++-----
 .../RecommendationList/RecommendationList.scss     | 14 -----
 .../RecommendationList/RecommendationList.tsx      | 42 +++++++-------
 src/client/views/newlightbox/utils.ts              | 31 +++++++----
 src/client/views/search/SearchBox.scss             | 65 +++++++++++++++++-----
 src/client/views/search/SearchBox.tsx              | 57 +++++++++++++++++--
 7 files changed, 160 insertions(+), 95 deletions(-)

(limited to 'src/client/views/newlightbox/Header/LightboxHeader.tsx')

diff --git a/src/client/views/newlightbox/Header/LightboxHeader.scss b/src/client/views/newlightbox/Header/LightboxHeader.scss
index 2872a383b..a9e60ea98 100644
--- a/src/client/views/newlightbox/Header/LightboxHeader.scss
+++ b/src/client/views/newlightbox/Header/LightboxHeader.scss
@@ -59,26 +59,7 @@
             background: $blue-l1;
         }
     }
-
-    .lb-button2 {
-        padding: 3px;
-        cursor: pointer;
-        display: flex;
-        flex-direction: row;
-        justify-content: space-evenly;
-        align-items: center;
-        transition: 0.2s ease;
-        gap: 5px; 
-        font-size: 15px;
-        height: fit-content;
-        border-radius: 3px;
-
-        &:hover {
-            background: $gray-l2;
-            transform: scale(1.01);
-        }
-    }
-  
+    
     &.dark {
       background: $black;
     }
diff --git a/src/client/views/newlightbox/Header/LightboxHeader.tsx b/src/client/views/newlightbox/Header/LightboxHeader.tsx
index a542d2943..a272ce294 100644
--- a/src/client/views/newlightbox/Header/LightboxHeader.tsx
+++ b/src/client/views/newlightbox/Header/LightboxHeader.tsx
@@ -5,21 +5,23 @@ import { NewLightboxView } from '../NewLightboxView';
 import { StrCast } from '../../../../fields/Types';
 import { EditableText } from '../components/EditableText';
 import { getType } from '../utils';
-import { Button, Size, Type } from 'browndash-components';
+import { Button, IconButton, Size, Type } from 'browndash-components';
 import { MdExplore, MdTravelExplore } from 'react-icons/md'
 import { BsBookmark, BsBookmarkFill } from 'react-icons/bs'
 import { Doc } from '../../../../fields/Doc';
+import { LightboxView } from '../../LightboxView';
+import { Colors } from '../../global/globalEnums';
 
 
 export const NewLightboxHeader = (props: INewLightboxHeader) => {
     const {height = 100, width} = props;
-    const [doc, setDoc] = React.useState<Doc | undefined>(NewLightboxView.LightboxDoc)
+    const [doc, setDoc] = React.useState<Doc | undefined>(LightboxView.LightboxDoc)
     const [editing, setEditing] = React.useState<boolean>(false)
     const [title, setTitle] = React.useState<JSX.Element | null>(
         (null)
     )
     React.useEffect(() => {
-        let lbDoc = NewLightboxView.LightboxDoc
+        let lbDoc = LightboxView.LightboxDoc
         setDoc(lbDoc)
         if (lbDoc) {
             setTitle(
@@ -32,7 +34,7 @@ export const NewLightboxHeader = (props: INewLightboxHeader) => {
                 setEditing={setEditing}            
             />)
         }
-    }, [NewLightboxView.LightboxDoc])
+    }, [LightboxView.LightboxDoc])
 
     const [saved, setSaved] = React.useState<boolean>(false)
 
@@ -47,21 +49,14 @@ export const NewLightboxHeader = (props: INewLightboxHeader) => {
             <div className={`type`}>{getType(StrCast(doc.type))}</div>
         </div>
         <div style={{gridColumn: 2, gridRow: 1, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center'}}>
-            <div className={`lb-button2`} onClick={() => setSaved(!saved)}>
-                {saved ? <BsBookmarkFill/> : <BsBookmark/>}
-            </div>
-            <div className={`lb-button2`} onClick={() => setSaved(!saved)}>
-                {saved ? <BsBookmarkFill/> : <BsBookmark/>}
-            </div>
+            <IconButton size={Size.XSMALL} onClick={() => setSaved(!saved)} color={Colors.DARK_GRAY} icon={saved ? <BsBookmarkFill/> : <BsBookmark/>}/>
+            <IconButton size={Size.XSMALL} onClick={() => setSaved(!saved)} color={Colors.DARK_GRAY} icon={saved ? <BsBookmarkFill/> : <BsBookmark/>}/>
         </div>
         <div  style={{gridColumn: 2, gridRow: 2, height: '100%', display: 'flex', justifyContent: 'flex-end', alignItems: 'center'}}>
-            <div className={`lb-button ${NewLightboxView.ExploreMode}`} onClick={() => {
+            <Button onClick={() => {
                 console.log(NewLightboxView.ExploreMode)
                 NewLightboxView.SetExploreMode(!NewLightboxView.ExploreMode)
-            }}>
-                <MdTravelExplore/>
-                t-SNE 2D Embeddings
-            </div>
+            }} size={Size.XSMALL} color={Colors.DARK_GRAY} type={Type.SEC} text={"t-SNE 2D Embeddings"} icon={<MdTravelExplore/>}/>
         </div>
     </div>
 }
\ No newline at end of file
diff --git a/src/client/views/newlightbox/RecommendationList/RecommendationList.scss b/src/client/views/newlightbox/RecommendationList/RecommendationList.scss
index e59834353..40dd47e47 100644
--- a/src/client/views/newlightbox/RecommendationList/RecommendationList.scss
+++ b/src/client/views/newlightbox/RecommendationList/RecommendationList.scss
@@ -101,20 +101,6 @@
             min-width: 70px;
             height: 21.5px;
           }
-
-          .remove {
-            cursor: pointer;
-            display: flex;
-            justify-content: center;
-            align-items: center;
-            color: $text-color-lm;
-            padding: 1.5px;
-            border-radius: 2px;
-
-            &:hover {
-              background: $gray-l1;
-            }
-          }
         }
 
       }
diff --git a/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx
index 674a501a7..9f3c32e4e 100644
--- a/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx
+++ b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx
@@ -12,6 +12,8 @@ import { IBounds } from '../ExploreView/utils';
 import { List } from '../../../../fields/List';
 import { Id } from '../../../../fields/FieldSymbols';
 import { LightboxView } from '../../LightboxView';
+import { IconButton, Size, Type } from 'browndash-components';
+import { Colors } from '../../global/globalEnums';
 
 export const RecommendationList = (props: IRecommendationList) => {
     const {loading, keywords} = props
@@ -31,8 +33,9 @@ export const RecommendationList = (props: IRecommendationList) => {
     React.useEffect(() => {
         const getKeywords = async () => {
             let text = StrCast(LightboxView.LightboxDoc?.text)
-            console.log('fetching keywords w/: ', text)
+            console.log('[1] fetching keywords')
             const response = await fetchKeywords(text, 5, true)
+            console.log('[2] response:', response)
             const kw = response.keywords;
             console.log(kw);
             NewLightboxView.SetKeywords(kw);
@@ -59,30 +62,30 @@ export const RecommendationList = (props: IRecommendationList) => {
     React.useEffect(() => {
         const getRecommendations = async () => {
             console.log('fetching recommendations')
-            let query = undefined
+            let query = 'undefined'
             if (keywordsLoc) query = keywordsLoc.join(',')
             let src = StrCast(NewLightboxView.LightboxDoc?.text)
             let dashDocs:IDocRequest[] = [];
             // get linked docs
             let linkedDocs = DocListCast(NewLightboxView.LightboxDoc?.links)
             console.log("linked docs", linkedDocs)
-            // get context docs
-            let contextDocs: Doc[] = DocListCast(DocCast(NewLightboxView.LightboxDoc?.context).data)
-            let docId = NewLightboxView.LightboxDoc && NewLightboxView.LightboxDoc[Id]
-            console.log("context docs", contextDocs)
-            contextDocs.forEach((doc: Doc) => {
-                if (docId !== doc[Id]){
-                    dashDocs.push({
-                        title: StrCast(doc.title),
-                        text: StrCast(doc.text),
-                        id: doc[Id],
-                        type: StrCast(doc.type)
-                    })
-                }
-            })
+            // get context docs (docs that are also in the collection)
+            // let contextDocs: Doc[] = DocListCast(DocCast(LightboxView.LightboxDoc?.context).data)
+            // let docId = LightboxView.LightboxDoc && LightboxView.LightboxDoc[Id]
+            // console.log("context docs", contextDocs)
+            // contextDocs.forEach((doc: Doc) => {
+            //     if (docId !== doc[Id]){
+            //         dashDocs.push({
+            //             title: StrCast(doc.title),
+            //             text: StrCast(doc.text),
+            //             id: doc[Id],
+            //             type: StrCast(doc.type)
+            //         })
+            //     }
+            // })
             console.log("dash docs", dashDocs)
             if (query !== undefined) {
-                const response = await fetchRecommendations(src, query, dashDocs, true)
+                const response = await fetchRecommendations(src, query, [], true)
                 const num_recs = response.num_recommendations
                 const recs = response.recommendations
                 const keywords = response.keywords
@@ -131,6 +134,7 @@ export const RecommendationList = (props: IRecommendationList) => {
                         return a.distance - b.distance
                     } else return 0
                 })
+                console.log("[rec]: ", recommendations)
                 NewLightboxView.SetRecs(recommendations)
                 setRecs(recommendations)
             }
@@ -160,11 +164,11 @@ export const RecommendationList = (props: IRecommendationList) => {
                 {keywordsLoc && keywordsLoc.map((word, ind) => {
                     return <div className={`keyword`}>
                         {word}
-                        <div className={`remove`} onClick={() => {
+                        <IconButton type={Type.PRIM} size={Size.XSMALL} color={Colors.DARK_GRAY} icon={<GrClose/>} onClick={() => {
                             let kw = keywordsLoc
                             kw.splice(ind)
                             NewLightboxView.SetKeywords(kw)
-                        }}>{<GrClose/>}</div>
+                        }}/>
                     </div>
                 })}
             </div>
diff --git a/src/client/views/newlightbox/utils.ts b/src/client/views/newlightbox/utils.ts
index 29b83c4e0..6016abca4 100644
--- a/src/client/views/newlightbox/utils.ts
+++ b/src/client/views/newlightbox/utils.ts
@@ -9,9 +9,18 @@ export interface IDocRequest {
 }
 
 export const fetchRecommendations = async (src: string, query: string, docs?: IDocRequest[], dummy?: boolean) => {
-    console.log("making request with: ", query)
+    console.log("[rec] making request")
     if (dummy) {
-        return dummyRecs;
+        return {
+            "recommendations": dummyRecs,
+            "keywords": dummyKeywords,
+            "num_recommendations": 4,
+            "max_x": 100,
+            "max_y": 100,
+            "min_x": 0,
+            "min_y": 0
+
+        };
     }
     const response = await fetch('http://127.0.0.1:8000/recommend', {
         method: 'POST',
@@ -31,9 +40,11 @@ export const fetchRecommendations = async (src: string, query: string, docs?: ID
 }
 
 export const fetchKeywords = async (text: string, n: number, dummy?: boolean) => {
-    console.log("making request with: ", text)
+    console.log("[fetchKeywords]")
     if (dummy) {
-        return dummyKeywords;
+        return {
+            "keywords": dummyKeywords
+        };
     }
     const response = await fetch('http://127.0.0.1:8000/keywords', {
         method: 'POST',
@@ -69,8 +80,8 @@ export const getType = (type: DocumentType | string) => {
     }
 }
 
-const dummyRecs: IRecommendation[] = [
-    {
+const dummyRecs = {
+    "a": {
         title: 'Vannevar Bush - American Engineer',
         previewUrl: 'https://cdn.britannica.com/98/23598-004-1E6A382E/Vannevar-Bush-Differential-Analyzer-1935.jpg', 
         type: 'web',
@@ -82,14 +93,14 @@ const dummyRecs: IRecommendation[] = [
             y: 0
         }
     },
-    {
+    "b": {
         title: "From Memex to hypertext: Vannevar Bush and the mind's machine",
         type: 'pdf',
         distance: 5.4,
         source: 'Google Scholar',
         related_concepts: ['memex', 'vannevar bush', 'hypertext'],
     },
-    {
+    "c": {
         title: 'How the hyperlink changed everything | Small Thing Big Idea, a TED series',
         previewUrl: 'https://pi.tedcdn.com/r/talkstar-photos.s3.amazonaws.com/uploads/b17d043f-2642-4117-a913-52204505513f/MargaretGouldStewart_2018V-embed.jpg?u%5Br%5D=2&u%5Bs%5D=0.5&u%5Ba%5D=0.8&u%5Bt%5D=0.03&quality=82w=640', 
         type: 'youtube',
@@ -97,7 +108,7 @@ const dummyRecs: IRecommendation[] = [
         source: 'www.youtube.com',
         related_concepts: ['User Control', 'Explanations']
     },
-    {
+    "d": {
         title: 'Recommender Systems: Behind the Scenes of Machine Learning-Based Personalization',
         previewUrl: 'https://sloanreview.mit.edu/wp-content/uploads/2018/10/MAG-Ransbotham-Ratings-Recommendations-1200X627-1200x627.jpg', 
         type: 'pdf',
@@ -105,6 +116,6 @@ const dummyRecs: IRecommendation[] = [
         source: 'www.altexsoft.com',
         related_concepts: ['User Control', 'Explanations']
     }
-]
+}
 
 const dummyKeywords = ['user control', 'vannevar bush', 'hypermedia', 'hypertext']
\ No newline at end of file
diff --git a/src/client/views/search/SearchBox.scss b/src/client/views/search/SearchBox.scss
index e8865b918..5516cf205 100644
--- a/src/client/views/search/SearchBox.scss
+++ b/src/client/views/search/SearchBox.scss
@@ -9,16 +9,21 @@
     background: none;
     z-index: 1000;
     padding: 0px;
+    overflow: scroll;
     cursor: default;
 
     .searchBox-bar {
         width: 100%;
-        height: 35px;
+        height: fit-content;
         display: flex;
         justify-content: center;
         align-items: center;
         background-color: none;
         padding: 5px;
+        top: 0px;
+        position: sticky;
+        background: $light-gray;
+        border-bottom: $standard-border;
 
         .searchBox-type {
             display: block;
@@ -42,34 +47,66 @@
         }
     }
 
-    .searchBox-results-container {
+    .section-header {
+
+        .section-title {
+            font-size: $body-text;
+            font-weight: 600;
+        }
+
+        .section-subtitle {
+            display: flex;
+            color: $light-gray;
+        }
+        
+        padding: 5px 10px;
+        display: flex;
+        flex-direction: column;
+        gap: 3px;
+        background: $medium-blue;
+        color: white;
+    }
+
+    .searchBox-recommendations-container {
         display: flex;
         flex-direction: column;
         width: 100%;
-        height: 100%;
+        height: fit-content;
         justify-content: "center";
-    
-        .searchBox-results-count {
+
+        .searchBox-recommendations-view {
+            margin-top: 10px;
             display: flex;
-            color: gray;
-            margin-left: 5px;
+            width: 100%;
+            height: fit-content;
+            flex-direction: column;
+            gap: 10px;
+            padding: 0px 10px;
+            
+
         }
+    }
+
+    .searchBox-results-container {
+        display: flex;
+        flex-direction: column;
+        width: 100%;
+        height: fit-content;
+        justify-content: "center";
     
-        .searchBox-results-scroll-view {
-            margin-top: 10px;
+        .searchBox-results-view {
             display: inline-block;
             width: 100%;
-            height: calc(100% - 55px);
-            overflow-y: scroll;
+            height: fit-content;
 
             .searchBox-results-scroll-view-result {
                 display: inline-block;
                 vertical-align: middle;
                 width: 100%;
-                height: 50px;
+                height: fit-content;
                 cursor: pointer;
                 font-size: 15px;
-                padding: 11px;
+                padding: 10px;
 
                 &.searchBox-results-scroll-view-result-selected {
                     background: #999;
@@ -81,6 +118,8 @@
                     width: calc(100% - 45px);
                     text-align: left;
                     overflow: hidden;
+                    max-height: 2.4em;
+                    line-height: 1.2em;
                     text-overflow: ellipsis;
                 }
 
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 3479cd20f..43e5344b6 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -14,6 +14,8 @@ import { CollectionDockingView } from '../collections/CollectionDockingView';
 import { ViewBoxBaseComponent } from '../DocComponent';
 import { FieldView, FieldViewProps } from '../nodes/FieldView';
 import './SearchBox.scss';
+import { fetchRecommendations } from '../newlightbox/utils';
+import { IRecommendation, Recommendation } from '../newlightbox/components';
 
 const DAMPENING_FACTOR = 0.9;
 const MAX_ITERATIONS = 25;
@@ -42,6 +44,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
     @observable _searchString = '';
     @observable _docTypeString = 'all';
     @observable _results: Map<Doc, string[]> = new Map<Doc, string[]>();
+    @observable _recommendations: IRecommendation[] = [];
     @observable _pageRanks: Map<Doc, number> = new Map<Doc, number>();
     @observable _linkedDocsOut: Map<Doc, Set<Doc>> = new Map<Doc, Set<Doc>>();
     @observable _linkedDocsIn: Map<Doc, Set<Doc>> = new Map<Doc, Set<Doc>>();
@@ -393,6 +396,38 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
 
         if (query) {
             this.searchCollection(query);
+            const response = await fetchRecommendations('', query, [], true)
+            const recs = response.recommendations
+            const recommendations:IRecommendation[] = []
+            for (const key in recs) {
+                const title = recs[key].title;
+                console.log(title);
+                const url = recs[key].url
+                const type = recs[key].type
+                const text = recs[key].text
+                const transcript = recs[key].transcript
+                const previewUrl = recs[key].previewUrl
+                const embedding = recs[key].embedding
+                const distance = recs[key].distance
+                const source = recs[key].source
+                const related_concepts = recs[key].related_concepts
+                const docId = recs[key].doc_id
+                recommendations.push({
+                    title: title,
+                    data: url,
+                    type: type,
+                    text: text,
+                    transcript: transcript,
+                    previewUrl: previewUrl,
+                    embedding: embedding,
+                    distance: Math.round(distance * 100) / 100,
+                    source: source,
+                    related_concepts: related_concepts,
+                    docId: docId
+                })
+            }
+            const setRecommendations = action(() => this._recommendations = recommendations)
+            setRecommendations()
         }
     };
 
@@ -487,6 +522,10 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
             }
         });
 
+        const recommendationsJSX: JSX.Element[] = this._recommendations.map((props) => (
+            <Recommendation {...props}/>
+        ))
+
         return (
             <div style={{ pointerEvents: 'all' }} className="searchBox-container">
                 <div className="searchBox-bar">
@@ -511,10 +550,20 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() {
                         ref={this._inputRef}
                     />
                 </div>
-                <div className="searchBox-results-container">
-                    <div className="searchBox-results-count">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
-                    <div className="searchBox-results-scroll-view">{resultsJSX}</div>
-                </div>
+                {resultsJSX.length > 0 && <div className="searchBox-results-container">
+                    <div className="section-header">
+                        <div className="section-title">Results</div>
+                        <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
+                    </div>
+                    <div className="searchBox-results-view">{resultsJSX}</div>
+                </div>}
+                {recommendationsJSX.length > 0 && <div className="searchBox-recommendations-container">
+                    <div className="section-header">
+                        <div className="section-title">Recommendations</div>
+                        <div className="section-subtitle">{`${validResults}` + ' result' + (validResults === 1 ? '' : 's')}</div>
+                    </div>
+                    <div className="searchBox-recommendations-view">{recommendationsJSX}</div>
+                </div>}
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2

