diff options
author | ab <abdullah_ahmed@brown.edu> | 2019-08-13 17:26:49 -0400 |
---|---|---|
committer | ab <abdullah_ahmed@brown.edu> | 2019-08-13 17:26:49 -0400 |
commit | e03a1b2cc90e0fdb7789f4826e482e9040aa7075 (patch) | |
tree | 8704de27d354bc20512a6b8058df3e4ba4c1bb64 | |
parent | 843ac47f04f8c9bd687600f9f07fe4c15744e4ec (diff) |
80% done, garbage collection is much needed
-rw-r--r-- | src/Recommendations.scss | 21 | ||||
-rw-r--r-- | src/Recommendations.tsx | 28 | ||||
-rw-r--r-- | src/client/util/SearchUtil.ts | 7 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/Recommendations.scss | 65 | ||||
-rw-r--r-- | src/client/views/Recommendations.tsx | 169 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 13 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 34 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 21 |
9 files changed, 297 insertions, 63 deletions
diff --git a/src/Recommendations.scss b/src/Recommendations.scss deleted file mode 100644 index 5129a59d9..000000000 --- a/src/Recommendations.scss +++ /dev/null @@ -1,21 +0,0 @@ -.recommendation-content *{ - display: inline-block; - margin: auto; - border: 1px dashed grey; - padding: 2px 2px; -} - -.recommendation-content { - float: left; - border: 1px solid green; - width: 200px; - align-content: center; -} - -.rec-scroll { - overflow-y: scroll; - height: 300px; - width: auto; - position: absolute; - background: #cdcdcd; -}
\ No newline at end of file diff --git a/src/Recommendations.tsx b/src/Recommendations.tsx deleted file mode 100644 index ca1123ef9..000000000 --- a/src/Recommendations.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { observer } from "mobx-react"; -import React = require("react"); -import { Doc } from "./new_fields/Doc"; -import { NumCast } from "./new_fields/Types"; - -export interface RecProps { - documents: { preview: string, similarity: number }[], - node: Doc; -} - -@observer -export class Recommendations extends React.Component<RecProps> { - render() { - const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px" - return ( - <div className="rec-scroll" style={{ transform: transform }}> - {this.props.documents.map(doc => { - return ( - <div className="recommendation-content"> - <img src={doc.preview} /> - <div>{doc.similarity}</div> - </div> - ) - })} - </div> - ) - } -}
\ No newline at end of file diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 1fce995d7..85e593529 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -81,12 +81,13 @@ export namespace SearchUtil { export async function GetAllDocs() { const query = "*"; let response = await rp.get(Utils.prepend('/search'), { - qs: { - q: query - } + qs: + { start: 0, rows: 10000, q: query }, + }); let result: IdSearchResult = JSON.parse(response); const { ids, numFound, highlighting } = result; + console.log(ids.length); const docMap = await DocServer.GetRefFields(ids); const docs: Doc[] = []; for (const id of ids) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 419b15697..0b6fe3876 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -41,6 +41,7 @@ import { ClientUtils } from '../util/ClientUtils'; import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField'; //import { DocumentManager } from '../util/DocumentManager'; import { DictationManager } from '../util/DictationManager'; +import { Recommendations } from './Recommendations'; @observer export class MainView extends React.Component { @@ -581,6 +582,7 @@ export class MainView extends React.Component { {this.mainContent} <PreviewCursor /> <ContextMenu /> + <Recommendations /> {this.nodesMenu()} {this.miscButtons} <PDFMenu /> diff --git a/src/client/views/Recommendations.scss b/src/client/views/Recommendations.scss new file mode 100644 index 000000000..5d8f17e37 --- /dev/null +++ b/src/client/views/Recommendations.scss @@ -0,0 +1,65 @@ +@import "globalCssVariables"; + +.rec-content *{ + display: inline-block; + margin: auto; + width: 50; + height: 30px; + border: 1px dashed grey; + padding: 10px 10px; +} + +.rec-content { + float: left; + width: inherit; + align-content: center; +} + +.rec-scroll { + overflow-y: scroll; + overflow-x: hidden; + position: absolute; + // display: flex; + z-index: 10000; + box-shadow: gray 0.2vw 0.2vw 0.4vw; + // flex-direction: column; + background: whitesmoke; + padding-bottom: 10px; + border-radius: 15px; + border: solid #BBBBBBBB 1px; + width: 200px; + text-align: center; + max-height: 250px; + text-transform: uppercase; + color: grey; + letter-spacing: 2px; +} + +.content { + padding: 10px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +.image-background { + pointer-events: none; + background-color: transparent; + width: 50%; + text-align: center; + height: 35px; + margin-left: 5px; +} + +img{ + width: 100%; + height: 100%; +} + +.score { + // margin-left: 15px; + width: 50%; + height: 100%; + text-align: center; +} diff --git a/src/client/views/Recommendations.tsx b/src/client/views/Recommendations.tsx new file mode 100644 index 000000000..8569996b3 --- /dev/null +++ b/src/client/views/Recommendations.tsx @@ -0,0 +1,169 @@ +import { observer } from "mobx-react"; +import React = require("react"); +import { observable, action } from "mobx"; +import Measure from "react-measure"; +import "./Recommendations.scss"; +import { Doc, DocListCast, WidthSym, HeightSym } from "../../new_fields/Doc"; +import { DocumentIcon } from "./nodes/DocumentIcon"; +import { StrCast, NumCast } from "../../new_fields/Types"; +import { returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../Utils"; +import { Transform } from "../util/Transform"; +import { ObjectField } from "../../new_fields/ObjectField"; +import { DocumentView } from "./nodes/DocumentView"; +import { DocumentType } from "../documents/Documents"; + + +export interface RecProps { + documents: { preview: Doc, similarity: number }[]; + node: Doc; +} + +@observer +export class Recommendations extends React.Component<{}> { + + static Instance: Recommendations; + @observable private _display: boolean = false; + @observable private _pageX: number = 0; + @observable private _pageY: number = 0; + @observable private _width: number = 0; + @observable private _height: number = 0; + @observable private _documents: { preview: Doc, score: number }[] = []; + + constructor(props: {}) { + super(props); + Recommendations.Instance = this; + } + + private DocumentIcon(doc: Doc) { + let layoutresult = StrCast(doc.type); + let renderDoc = doc; + //let box: number[] = []; + if (layoutresult.indexOf(DocumentType.COL) !== -1) { + renderDoc = Doc.MakeDelegate(renderDoc); + let bounds = DocListCast(renderDoc.data).reduce((bounds, doc) => { + var [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; + let [bptX, bptY] = [sptX + doc[WidthSym](), sptY + doc[HeightSym]()]; + return { + x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), + r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) + }; + }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); + } + let returnXDimension = () => 50; + let returnYDimension = () => 50; + let scale = () => returnXDimension() / NumCast(renderDoc.nativeWidth, returnXDimension()); + let newRenderDoc = Doc.MakeDelegate(renderDoc); /// newRenderDoc -> renderDoc -> render"data"Doc -> TextProt + const docview = <div> + {/* onPointerDown={action(() => { + this._useIcons = !this._useIcons; + this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE); + })} + onPointerEnter={action(() => this._displayDim = this._useIcons ? 50 : Number(SEARCH_THUMBNAIL_SIZE))} + onPointerLeave={action(() => this._displayDim = 50)} > */} + <DocumentView + fitToBox={StrCast(doc.type).indexOf(DocumentType.COL) !== -1} + Document={doc} + addDocument={returnFalse} + removeDocument={returnFalse} + ScreenToLocalTransform={Transform.Identity} + addDocTab={returnFalse} + renderDepth={1} + PanelWidth={returnXDimension} + PanelHeight={returnYDimension} + focus={emptyFunction} + backgroundColor={returnEmptyString} + selectOnLoad={false} + parentActive={returnFalse} + whenActiveChanged={returnFalse} + bringToFront={emptyFunction} + zoomToScale={emptyFunction} + getScale={returnOne} + ContainingCollectionView={undefined} + ContentScaling={scale} + /> + </div>; + const data = renderDoc.data; + if (data instanceof ObjectField) newRenderDoc.data = ObjectField.MakeCopy(data); + newRenderDoc.preview = true; + return docview; + + } + + @action + closeMenu = () => { + this._display = false; + } + + @action + resetDocuments = () => { + this._documents = []; + } + + @action + addDocuments = (documents: { preview: Doc, score: number }[]) => { + this._documents = documents; + } + + @action + displayRecommendations(x: number, y: number) { + this._pageX = x; + this._pageY = y; + this._display = true; + } + + static readonly buffer = 20; + + get pageX() { + const x = this._pageX; + if (x < 0) { + return 0; + } + const width = this._width; + if (x + width > window.innerWidth - Recommendations.buffer) { + return window.innerWidth - Recommendations.buffer - width; + } + return x; + } + + get pageY() { + const y = this._pageY; + if (y < 0) { + return 0; + } + const height = this._height; + if (y + height > window.innerHeight - Recommendations.buffer) { + return window.innerHeight - Recommendations.buffer - height; + } + return y; + } + + render() { + if (!this._display) { + return null; + } + let style = { left: this.pageX, top: this.pageY }; + //const transform = "translate(" + (NumCast(this.props.node.x) + 350) + "px, " + NumCast(this.props.node.y) + "px" + return ( + <Measure offset onResize={action((r: any) => { this._width = r.offset.width; this._height = r.offset.height; })}> + {({ measureRef }) => ( + <div className="rec-scroll" style={style} ref={measureRef}> + <p>Recommendations</p> + {this._documents.map(doc => { + return ( + <div className="content"> + <span className="image-background"> + {this.DocumentIcon(doc.preview)} + </span> + <span className="score">{doc.score}</span> + </div> + ); + })} + + </div> + ) + } + + </Measure> + ); + } +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d1e8031fd..50f7e2dc8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -896,10 +896,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { allDocs.forEach(doc => console.log(doc.title)); // clears internal representation of documents as vectors ClientRecommender.Instance.reset_docs(); - await Promise.all(activedocs.map((doc: Doc) => { - //console.log(StrCast(doc.title)); - const extdoc = doc.data_ext as Doc; - return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc); + await Promise.all(allDocs.map((doc: Doc) => { + console.log(StrCast(doc.title)); + if (doc.type === DocumentType.IMG) { + console.log(doc.title); + const extdoc = doc.data_ext as Doc; + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc); + } })); console.log(ClientRecommender.Instance.createDistanceMatrix()); }, @@ -1013,7 +1016,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire return <div className={freeformclass} style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}> {this.props.children} - <ClientRecommender title="Distance Matrix" /> + {/* <ClientRecommender title="Distance Matrix" /> */} </div>; } }
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 24bcc0217..3ce4dbd4f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -40,9 +40,13 @@ import React = require("react"); import { DictationManager } from '../../util/DictationManager'; import { MainView } from '../MainView'; import requestPromise = require('request-promise'); -import { Recommendations } from '../../../Recommendations'; +import { Recommendations } from '../Recommendations'; +import { SearchUtil } from '../../util/SearchUtil'; +import { ClientRecommender } from '../../ClientRecommender'; +import { DocumentType } from '../../documents/Documents'; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? +library.add(fa.faBrain); library.add(fa.faTrash); library.add(fa.faShare); library.add(fa.faDownload); @@ -610,6 +614,33 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu a.click(); } }); + cm.addItem({ + description: "Recommender System", + event: async () => { + if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" }); + let documents: Doc[] = []; + let allDocs = await SearchUtil.GetAllDocs(); + allDocs.forEach(doc => console.log(doc.title)); + // clears internal representation of documents as vectors + ClientRecommender.Instance.reset_docs(); + await Promise.all(allDocs.map((doc: Doc) => { + if (doc.type === DocumentType.IMG) { + console.log(StrCast(doc.title)); + documents.push(doc); + const extdoc = doc.data_ext as Doc; + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc); + } + })); + console.log(ClientRecommender.Instance.createDistanceMatrix()); + let recDocs: { preview: Doc, score: number }[] = []; + for (let i = 0; i < documents.length; i++) { + recDocs.push({ preview: documents[i], score: i }); + } + Recommendations.Instance.addDocuments(recDocs); + Recommendations.Instance.displayRecommendations(e.pageX + 100, e.pageY); + }, + icon: "brain" + }); cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" }); type User = { email: string, userDocumentId: string }; let usersMenu: ContextMenuProps[] = []; @@ -758,7 +789,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu </div> } </div> - <Recommendations documents={documents} node={this.props.Document}></Recommendations> </div> ); } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 73b892e26..45d389ba6 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -28,6 +28,9 @@ import FaceRectangles from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); +import { SearchUtil } from '../../util/SearchUtil'; +import { ClientRecommender } from '../../ClientRecommender'; +import { DocumentType } from '../../documents/Documents'; var requestImageSize = require('../../util/request-image-size'); var path = require('path'); const { Howl } = require('howler'); @@ -240,10 +243,20 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD } } - extractText = () => { - //Recommender.Instance.extractText(this.dataDoc, this.extensionDoc); - // request recommender - //fetch(Utils.prepend("/recommender"), { body: body, method: "POST", headers: { "content-type": "application/json" } }).then((value) => console.log(value)); + extractText = async () => { + //let activedocs = this.getActiveDocuments(); + let allDocs = await SearchUtil.GetAllDocs(); + allDocs.forEach(doc => console.log(doc.title)); + // clears internal representation of documents as vectors + ClientRecommender.Instance.reset_docs(); + await Promise.all(allDocs.map((doc: Doc) => { + //console.log(StrCast(doc.title)); + if (doc.type === DocumentType.IMG) { + const extdoc = doc.data_ext as Doc; + return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc); + } + })); + console.log(ClientRecommender.Instance.createDistanceMatrix()); } generateMetadata = (threshold: Confidence = Confidence.Excellent) => { |