aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/newlightbox/RecommendationList
diff options
context:
space:
mode:
authorSophie Zhang <sophie_zhang@brown.edu>2023-07-18 12:21:17 -0400
committerSophie Zhang <sophie_zhang@brown.edu>2023-07-18 12:21:17 -0400
commit8410cd330b676ce50948b2ec1011a72b219ee87b (patch)
tree2009a9cd1942cf9662786fdbc2b20c6f0713cb60 /src/client/views/newlightbox/RecommendationList
parent2bfad0eb9e3d8f8d26f66cf8e6daa801a694cab0 (diff)
parent4e1bc2547787e9b1978c23da2045eb46407e1e3c (diff)
Merge branch 'master' into sophie-report-manager
Diffstat (limited to 'src/client/views/newlightbox/RecommendationList')
-rw-r--r--src/client/views/newlightbox/RecommendationList/RecommendationList.scss117
-rw-r--r--src/client/views/newlightbox/RecommendationList/RecommendationList.tsx196
-rw-r--r--src/client/views/newlightbox/RecommendationList/index.ts1
-rw-r--r--src/client/views/newlightbox/RecommendationList/utils.ts9
4 files changed, 323 insertions, 0 deletions
diff --git a/src/client/views/newlightbox/RecommendationList/RecommendationList.scss b/src/client/views/newlightbox/RecommendationList/RecommendationList.scss
new file mode 100644
index 000000000..40dd47e47
--- /dev/null
+++ b/src/client/views/newlightbox/RecommendationList/RecommendationList.scss
@@ -0,0 +1,117 @@
+@import '../NewLightboxStyles.scss';
+
+.recommendationlist-container {
+ height: calc(100% - 40px);
+ margin: 20px;
+ border-radius: 20px;
+ overflow-y: scroll;
+
+ .recommendations {
+ height: fit-content;
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ background: $gray-l1;
+ border-radius: 0px 0px 20px 20px;
+ }
+
+ .header {
+ top: 0px;
+ position: sticky;
+ background: $gray-l1;
+ border-bottom: $standard-border;
+ border-color: $gray-l2;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ border-radius: 20px 20px 0px 0px;
+ padding: 20px;
+ z-index: 2;
+ gap: 10px;
+ color: $text-color-lm;
+
+ .lb-label {
+ color: $gray-l3;
+ font-weight: $h1-weight;
+ font-size: $body-size;
+ }
+
+ .lb-caret {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ align-items: center;
+ gap: 5px;
+ cursor: pointer;
+ width: 100%;
+ user-select: none;
+ font-size: $body-size;
+ }
+
+ .more {
+ width: 100%;
+ }
+
+ &.dark {
+ color: $text-color-dm;
+ }
+
+ .title {
+ height: 30px;
+ min-height: 30px;
+ font-size: $h1-size;
+ font-weight: $h1-weight;
+ text-align: left;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+ }
+
+ .keywords {
+ display: flex;
+ flex-flow: row wrap;
+ gap: 5px;
+
+ .keyword-input {
+ padding: 3px 7px;
+ background: $gray-l2;
+ outline: none;
+ border: none;
+ height: 21.5px;
+ color: $text-color-lm;
+ }
+
+ .keyword {
+ padding: 3px 7px;
+ width: fit-content;
+ background: $gray-l2;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: row;
+ gap: 10px;
+ font-size: $body-size;
+ font-weight: $body-weight;
+
+ &.loading {
+ animation: skeleton-loading-l2 1s linear infinite alternate;
+ min-width: 70px;
+ height: 21.5px;
+ }
+ }
+
+ }
+ }
+
+ &.dark {
+ background: $black;
+ }
+
+ &.light,
+ &.default {
+ background: $gray-l1;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx
new file mode 100644
index 000000000..9f3c32e4e
--- /dev/null
+++ b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx
@@ -0,0 +1,196 @@
+import { GrClose } from 'react-icons/gr';
+import { IRecommendation, Recommendation } from "../components";
+import './RecommendationList.scss';
+import * as React from 'react';
+import { IRecommendationList } from "./utils";
+import { NewLightboxView } from '../NewLightboxView';
+import { DocCast, StrCast } from '../../../../fields/Types';
+import { FaCaretDown, FaCaretUp } from 'react-icons/fa';
+import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
+import { IDocRequest, fetchKeywords, fetchRecommendations } from '../utils';
+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
+ const [loadingKeywords, setLoadingKeywords] = React.useState<boolean>(true)
+ const [showMore, setShowMore] = React.useState<boolean>(false)
+ const [keywordsLoc, setKeywordsLoc] = React.useState<string[]>([])
+ const [update, setUpdate] = React.useState<boolean>(true)
+ const initialRecs: IRecommendation[] = [
+ {loading: true},
+ {loading: true},
+ {loading: true},
+ {loading: true},
+ {loading: true}
+ ];
+ const [recs, setRecs] = React.useState<IRecommendation[]>(initialRecs)
+
+ React.useEffect(() => {
+ const getKeywords = async () => {
+ let text = StrCast(LightboxView.LightboxDoc?.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);
+ if (LightboxView.LightboxDoc) {
+ console.log('setting keywords on doc')
+ LightboxView.LightboxDoc.keywords = new List<string>(kw);
+ setKeywordsLoc(NewLightboxView.Keywords);
+ }
+ setLoadingKeywords(false)
+ }
+ let keywordsList = StrListCast(LightboxView.LightboxDoc!.keywords)
+ if (!keywordsList || keywordsList.length < 2) {
+ setLoadingKeywords(true)
+ getKeywords()
+ setUpdate(!update)
+ } else {
+ setKeywordsLoc(keywordsList)
+ setLoadingKeywords(false)
+ setUpdate(!update)
+ }
+ }, [NewLightboxView.LightboxDoc])
+
+ // terms: vannevar bush, information spaces,
+ React.useEffect(() => {
+ const getRecommendations = async () => {
+ console.log('fetching recommendations')
+ 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 (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, [], true)
+ const num_recs = response.num_recommendations
+ const recs = response.recommendations
+ const keywords = response.keywords
+ const response_bounds: IBounds = {
+ max_x: response.max_x,
+ max_y: response.max_y,
+ min_x: response.min_x,
+ min_y: response.min_y
+ }
+ // if (NewLightboxView.NewLightboxDoc) {
+ // NewLightboxView.NewLightboxDoc.keywords = new List<string>(keywords);
+ // setKeywordsLoc(NewLightboxView.Keywords);
+ // }
+ // console.log(response_bounds)
+ NewLightboxView.SetBounds(response_bounds)
+ const recommendations: IRecommendation[] = [];
+ for (const key in recs) {
+ console.log(key)
+ const title = recs[key].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
+ related_concepts.length >= 1 && 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
+ })
+ }
+ recommendations.sort((a, b) => {
+ if (a.distance && b.distance) {
+ return a.distance - b.distance
+ } else return 0
+ })
+ console.log("[rec]: ", recommendations)
+ NewLightboxView.SetRecs(recommendations)
+ setRecs(recommendations)
+ }
+ }
+ getRecommendations();
+ }, [update])
+
+
+
+ return <div className={`recommendationlist-container`} onPointerDown={(e) => {e.stopPropagation()}}>
+ <div className={`header`}>
+ <div className={`title`}>
+ Recommendations
+ </div>
+ {NewLightboxView.LightboxDoc && <div style={{fontSize: 10}}>
+ The recommendations are produced based on the text in the document <b><u>{StrCast(NewLightboxView.LightboxDoc.title)}</u></b>. The following keywords are used to fetch the recommendations.
+ </div>}
+ <div className={`lb-label`}>Keywords</div>
+ {loadingKeywords ? <div className={`keywords`}>
+ <div className={`keyword ${loadingKeywords && 'loading'}`}/>
+ <div className={`keyword ${loadingKeywords && 'loading'}`}/>
+ <div className={`keyword ${loadingKeywords && 'loading'}`}/>
+ <div className={`keyword ${loadingKeywords && 'loading'}`}/>
+ </div>
+ :
+ <div className={`keywords`}>
+ {keywordsLoc && keywordsLoc.map((word, ind) => {
+ return <div className={`keyword`}>
+ {word}
+ <IconButton type={Type.PRIM} size={Size.XSMALL} color={Colors.DARK_GRAY} icon={<GrClose/>} onClick={() => {
+ let kw = keywordsLoc
+ kw.splice(ind)
+ NewLightboxView.SetKeywords(kw)
+ }}/>
+ </div>
+ })}
+ </div>
+ }
+ {!showMore ?
+ <div className={`lb-caret`} onClick={() => {setShowMore(true)}}>
+ More <FaCaretDown/>
+ </div>
+ :
+ <div className={`more`}>
+ <div className={`lb-caret`} onClick={() => {setShowMore(false)}}>
+ Less <FaCaretUp/>
+ </div>
+ <div className={`lb-label`}>Type</div>
+ <div className={`lb-label`}>Sources</div>
+ </div>
+ }
+ </div>
+ <div className={`recommendations`}>
+ {recs && recs.map((rec: IRecommendation) => {
+ return <Recommendation {...rec} />
+ })}
+ </div>
+ </div>
+} \ No newline at end of file
diff --git a/src/client/views/newlightbox/RecommendationList/index.ts b/src/client/views/newlightbox/RecommendationList/index.ts
new file mode 100644
index 000000000..f4555c1f2
--- /dev/null
+++ b/src/client/views/newlightbox/RecommendationList/index.ts
@@ -0,0 +1 @@
+export * from './RecommendationList' \ No newline at end of file
diff --git a/src/client/views/newlightbox/RecommendationList/utils.ts b/src/client/views/newlightbox/RecommendationList/utils.ts
new file mode 100644
index 000000000..cdfff3258
--- /dev/null
+++ b/src/client/views/newlightbox/RecommendationList/utils.ts
@@ -0,0 +1,9 @@
+import { IRecommendation } from "../components";
+
+export interface IRecommendationList {
+ loading?: boolean,
+ keywords?: string[],
+ recs?: IRecommendation[]
+ getRecs?: any
+}
+