diff options
author | Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> | 2023-06-07 11:50:45 -0400 |
---|---|---|
committer | Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> | 2023-06-07 11:50:45 -0400 |
commit | abd79b36f15466db05a2c1f379260b85e4ae838b (patch) | |
tree | 6d1c7b0e4d46ea7f3457d674ffe625c6b5fe859a | |
parent | cb4fbf58fb30b3ea863f5107674dd366566e39a7 (diff) |
added doc recommendations to search
7 files changed, 160 insertions, 95 deletions
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> ); } |