aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/ClientRecommender.scss12
-rw-r--r--src/client/ClientRecommender.tsx175
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts75
-rw-r--r--src/client/util/SearchUtil.ts25
-rw-r--r--src/client/views/MainView.tsx10
-rw-r--r--src/client/views/Recommendations.scss65
-rw-r--r--src/client/views/Recommendations.tsx169
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx29
-rw-r--r--src/client/views/nodes/DocumentView.tsx138
-rw-r--r--src/client/views/nodes/ImageBox.tsx24
10 files changed, 669 insertions, 53 deletions
diff --git a/src/client/ClientRecommender.scss b/src/client/ClientRecommender.scss
new file mode 100644
index 000000000..49163cdc8
--- /dev/null
+++ b/src/client/ClientRecommender.scss
@@ -0,0 +1,12 @@
+@import "/views/globalCssVariables.scss";
+
+.space{
+ border: 1px dashed blue;
+ border-spacing: 25px;
+ border-collapse: separate;
+ align-content: center;
+}
+
+.wrapper{
+ text-align: -webkit-center;
+} \ No newline at end of file
diff --git a/src/client/ClientRecommender.tsx b/src/client/ClientRecommender.tsx
new file mode 100644
index 000000000..63f85c737
--- /dev/null
+++ b/src/client/ClientRecommender.tsx
@@ -0,0 +1,175 @@
+import { Doc } from "../new_fields/Doc";
+import { StrCast } from "../new_fields/Types";
+import { List } from "../new_fields/List";
+import { CognitiveServices } from "./cognitive_services/CognitiveServices";
+import React = require("react");
+import { observer } from "mobx-react";
+import { observable, action, computed, reaction } from "mobx";
+var assert = require('assert');
+import "./ClientRecommender.scss";
+import { JSXElement } from "babel-types";
+
+export interface RecommenderProps {
+ title: string;
+}
+
+@observer
+export class ClientRecommender extends React.Component<RecommenderProps> {
+
+ static Instance: ClientRecommender;
+ private docVectors: Set<number[]>;
+ @observable private corr_matrix = [[0, 0], [0, 0]];
+
+ constructor(props: RecommenderProps) {
+ //console.log("creating client recommender...");
+ super(props);
+ if (!ClientRecommender.Instance) ClientRecommender.Instance = this;
+ this.docVectors = new Set<number[]>();
+ //this.corr_matrix = [[0, 0], [0, 0]];
+ }
+
+ @action
+ public reset_docs() {
+ this.docVectors = new Set();
+ this.corr_matrix = [[0, 0], [0, 0]];
+ }
+
+ /***
+ * Computes the cosine similarity between two vectors in Euclidean space.
+ */
+
+ private distance(vector1: number[], vector2: number[], metric: string = "cosine") {
+ assert(vector1.length === vector2.length, "Vectors are not the same length");
+ let similarity: number;
+ switch (metric) {
+ case "cosine":
+ var dotproduct = 0;
+ var mA = 0;
+ var mB = 0;
+ for (let i = 0; i < vector1.length; i++) { // here you missed the i++
+ dotproduct += (vector1[i] * vector2[i]);
+ mA += (vector1[i] * vector1[i]);
+ mB += (vector2[i] * vector2[i]);
+ }
+ mA = Math.sqrt(mA);
+ mB = Math.sqrt(mB);
+ similarity = (dotproduct) / ((mA) * (mB)); // here you needed extra brackets
+ return similarity;
+ case "euclidian":
+ var sum = 0;
+ for (let i = 0; i < vector1.length; i++) {
+ sum += Math.pow(vector1[i] - vector2[i], 2);
+ }
+ similarity = Math.sqrt(sum);
+ return similarity;
+ default:
+ return 0;
+ }
+ }
+
+ /***
+ * Computes the mean of a set of vectors
+ */
+
+ public mean(paragraph: Set<number[]>) {
+ const n = 200;
+ const num_words = paragraph.size;
+ let meanVector = new Array<number>(n).fill(0); // mean vector
+ if (num_words > 0) { // check to see if paragraph actually was vectorized
+ paragraph.forEach((wordvec: number[]) => {
+ for (let i = 0; i < n; i++) {
+ meanVector[i] += wordvec[i];
+ }
+ });
+ meanVector = meanVector.map(x => x / num_words);
+ this.addToDocSet(meanVector);
+ }
+ return meanVector;
+ }
+
+ private addToDocSet(vector: number[]) {
+ if (this.docVectors) {
+ this.docVectors.add(vector);
+ }
+ }
+
+ /***
+ * Uses Cognitive Services to extract keywords from a document
+ */
+
+ public async extractText(dataDoc: Doc, extDoc: Doc) {
+ let data = StrCast(dataDoc.title);
+ //console.log(data);
+ let converter = (results: any) => {
+ let keyterms = new List<string>();
+ results.documents.forEach((doc: any) => {
+ let keyPhrases = doc.keyPhrases;
+ keyPhrases.map((kp: string) => keyterms.push(kp));
+ });
+ return keyterms;
+ };
+ await CognitiveServices.Text.Appliers.analyzer(extDoc, ["key words"], data, converter);
+ }
+
+ /***
+ * Creates distance matrix for all Documents analyzed
+ */
+
+ @action
+ public createDistanceMatrix(documents: Set<number[]> = this.docVectors) {
+ const documents_list = Array.from(documents);
+ const n = documents_list.length;
+ var matrix = new Array<number>(n).fill(0).map(() => new Array<number>(n).fill(0));
+ for (let i = 0; i < n; i++) {
+ var doc1 = documents_list[i];
+ for (let j = 0; j < n; j++) {
+ var doc2 = documents_list[j];
+ matrix[i][j] = this.distance(doc1, doc2, "euclidian");
+ }
+ }
+ this.corr_matrix = matrix;
+ return matrix;
+ }
+
+ @computed
+ private get generateRows() {
+ const n = this.corr_matrix.length;
+ let rows: JSX.Element[] = [];
+ for (let i = 0; i < n; i++) {
+ let children: JSX.Element[] = [];
+ for (let j = 0; j < n; j++) {
+ //let cell = React.createElement("td", this.corr_matrix[i][j]);
+ let cell = <td>{this.corr_matrix[i][j].toFixed(4)}</td>;
+ children.push(cell);
+ }
+ //let row = React.createElement("tr", { children: children, key: i });
+ let row = <tr>{children}</tr>;
+ rows.push(row);
+ }
+ return rows;
+ }
+
+ render() {
+ return (<div className="wrapper">
+ <h3 >{this.props.title ? this.props.title : "hello"}</h3>
+ {/* <table className="space" >
+ <tbody>
+ <tr key="1">
+ <td key="1">{this.corr_matrix[0][0].toFixed(4)}</td>
+ <td key="2">{this.corr_matrix[0][1].toFixed(4)}</td>
+ </tr>
+ <tr key="2">
+ <td key="1">{this.corr_matrix[1][0].toFixed(4)}</td>
+ <td key="2">{this.corr_matrix[1][1].toFixed(4)}</td>
+ </tr>
+ </tbody>
+ </table> */}
+ <table className="space">
+ <tbody>
+ {this.generateRows}
+ </tbody>
+ </table>
+ </div>);
+ }
+
+} \ No newline at end of file
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 08fcb4883..75d0760ed 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -6,6 +6,9 @@ import { RouteStore } from "../../server/RouteStore";
import { Utils } from "../../Utils";
import { InkData } from "../../new_fields/InkField";
import { UndoManager } from "../util/UndoManager";
+import requestPromise = require("request-promise");
+import { List } from "../../new_fields/List";
+import { ClientRecommender } from "../ClientRecommender";
type APIManager<D> = { converter: BodyConverter<D>, requester: RequestExecutor };
type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise<string>;
@@ -19,7 +22,8 @@ export type Rectangle = { top: number, left: number, width: number, height: numb
export enum Service {
ComputerVision = "vision",
Face = "face",
- Handwriting = "handwriting"
+ Handwriting = "handwriting",
+ Text = "text"
}
export enum Confidence {
@@ -219,4 +223,73 @@ export namespace CognitiveServices {
}
+ export namespace Text {
+ export const Manager: APIManager<string> = {
+ converter: (data: string) => {
+ return JSON.stringify({
+ documents: [{
+ id: 1,
+ language: "en",
+ text: data
+ }]
+ });
+ },
+ requester: async (apiKey: string, body: string, service: Service) => {
+ let serverAddress = "https://eastus.api.cognitive.microsoft.com";
+ let endpoint = serverAddress + "/text/analytics/v2.1/keyPhrases";
+ let sampleBody = {
+ "documents": [
+ {
+ "language": "en",
+ "id": 1,
+ "text": "Hello world. This is some input text that I love."
+ }
+ ]
+ };
+ let actualBody = body;
+ const options = {
+ uri: endpoint,
+ body: actualBody,
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Ocp-Apim-Subscription-Key': apiKey
+ }
+
+ };
+ console.log("requested!");
+ return request.post(options);
+ }
+ };
+
+ export namespace Appliers {
+
+ export async function vectorize(keyterms: any) {
+ console.log("vectorizing...");
+ //keyterms = ["father", "king"];
+ let args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true };
+ await requestPromise.post(args).then(async (wordvecs) => {
+ var vectorValues = new Set<number[]>();
+ wordvecs.forEach((wordvec: any) => {
+ //console.log(wordvec.word);
+ vectorValues.add(wordvec.values as number[]);
+ });
+ ClientRecommender.Instance.mean(vectorValues);
+ //console.log(vectorValues.size);
+ });
+ }
+
+ export const analyzer = async (target: Doc, keys: string[], data: string, converter: Converter) => {
+ let results = await ExecuteQuery(Service.Text, Manager, data);
+ console.log(results);
+ let keyterms = converter(results);
+ //target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Key Word Analysis");
+ target[keys[0]] = keyterms;
+ console.log("analyzed!");
+ await vectorize(keyterms);
+ };
+ }
+
+ }
+
+
} \ No newline at end of file
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index ee5a83710..85e593529 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -77,4 +77,27 @@ export namespace SearchUtil {
aliasContexts.forEach(result => contexts.aliasContexts.push(...result.ids));
return contexts;
}
-} \ No newline at end of file
+
+ export async function GetAllDocs() {
+ const query = "*";
+ let response = await rp.get(Utils.prepend('/search'), {
+ 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) {
+ const field = docMap[id];
+ if (field instanceof Doc) {
+ docs.push(field);
+ }
+ }
+ return docs;
+ // const docs = ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc);
+ // return docs as Doc[];
+ }
+}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 6b856443b..57eb30439 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -38,6 +38,9 @@ import { OverlayView } from './OverlayView';
import PDFMenu from './pdf/PDFMenu';
import { PreviewCursor } from './PreviewCursor';
import { FilterBox } from './search/FilterBox';
+import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField';
+//import { DocumentManager } from '../util/DocumentManager';
+import { Recommendations } from './Recommendations';
import PresModeMenu from './presentationview/PresentationModeMenu';
import { PresBox } from './nodes/PresBox';
@@ -489,6 +492,12 @@ export class MainView extends React.Component {
</div >;
}
+ // clusterDocuments = () => {
+ // DocumentManager.Instance.DocumentViews();
+ // }
+
+
+
@action
@@ -557,6 +566,7 @@ export class MainView extends React.Component {
{this.miniPresentation}
<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 62f848a2b..2d4775070 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -37,6 +37,12 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso
import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
+import v5 = require("uuid/v5");
+import { ClientRecommender } from "../../../ClientRecommender";
+import { SearchUtil } from "../../../util/SearchUtil";
+import { SearchBox } from "../../SearchBox";
+import { RouteStore } from "../../../../server/RouteStore";
+import { string, number, elementType } from "prop-types";
import { DocServer } from "../../../DocServer";
import { FormattedTextBox } from "../../nodes/FormattedTextBox";
@@ -783,6 +789,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
+ @action
fitToContainer = async () => this.props.Document.fitToBox = !this.fitToBox;
arrangeContents = async () => {
@@ -850,6 +857,27 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
input.click();
}
});
+ ContextMenu.Instance.addItem({
+ description: "Recommender System",
+ event: async () => {
+ // if (!ClientRecommender.Instance) new ClientRecommender({ title: "Client Recommender" });
+ 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) {
+ console.log(doc.title);
+ const extdoc = doc.data_ext as Doc;
+ return ClientRecommender.Instance.extractText(doc, extdoc ? extdoc : doc);
+ }
+ }));
+ console.log(ClientRecommender.Instance.createDistanceMatrix());
+ },
+ icon: "brain"
+ });
layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" });
layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
layoutItems.push({
@@ -977,6 +1005,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" /> */}
</div>;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4d5307c88..2a6e91272 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -40,9 +40,14 @@ import { DocumentContentsView } from "./DocumentContentsView";
import "./DocumentView.scss";
import { FormattedTextBox } from './FormattedTextBox';
import React = require("react");
+import requestPromise = require('request-promise');
+import { Recommendations } from '../Recommendations';
+import { SearchUtil } from '../../util/SearchUtil';
+import { ClientRecommender } from '../../ClientRecommender';
import { DocumentType } from '../../documents/DocumentTypes';
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);
@@ -637,7 +642,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[] = [];
@@ -741,6 +772,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
render() {
+ let documents = [{ preview: "hi", similarity: 0 }];
let backgroundColor = this.layoutDoc.isBackground || (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground && this.layoutDoc.backgroundColor === this.layoutDoc.defaultBackgroundColor) ?
this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) :
StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc);
@@ -762,55 +794,63 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
let borderRounding = StrCast(Doc.GetProto(this.props.Document).borderRounding);
let localScale = this.props.ScreenToLocalTransform().Scale * brushDegree;
return (
- <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
- ref={this._mainCont}
- style={{
- pointerEvents: this.layoutDoc.isBackground && !this.isSelected() ? "none" : "all",
- color: foregroundColor,
- outlineColor: ["transparent", "maroon", "maroon"][brushDegree],
- outlineStyle: ["none", "dashed", "solid"][brushDegree],
- outlineWidth: brushDegree && !borderRounding ? `${localScale}px` : "0px",
- border: brushDegree && borderRounding ? `${["none", "dashed", "solid"][brushDegree]} ${["transparent", "maroon", "maroon"][brushDegree]} ${localScale}px` : undefined,
- borderRadius: "inherit",
- background: backgroundColor,
- width: nativeWidth,
- height: nativeHeight,
- transform: `scale(${this.props.ContentScaling()})`,
- opacity: this.Document.opacity
- }}
- onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
- onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
- >
- {!showTitle && !showCaption ? this.contents :
- <div style={{ position: "absolute", display: "inline-block", width: "100%", height: "100%", pointerEvents: "none" }}>
-
- <div style={{ width: "100%", height: showTextTitle ? "calc(100% - 29px)" : "100%", display: "inline-block", position: "absolute", top: showTextTitle ? "29px" : undefined }}>
- {this.contents}
- </div>
- {!showTitle ? (null) :
- <div style={{
- position: showTextTitle ? "relative" : "absolute", top: 0, padding: "4px", textAlign: "center", textOverflow: "ellipsis", whiteSpace: "pre",
- pointerEvents: SelectionManager.GetIsDragging() ? "none" : "all",
- overflow: "hidden", width: `${100 * this.props.ContentScaling()}%`, height: 25, background: "rgba(0, 0, 0, .4)", color: "white",
- transformOrigin: "top left", transform: `scale(${1 / this.props.ContentScaling()})`
- }}>
- <EditableView
- contents={(this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle]}
- display={"block"}
- height={72}
- fontSize={12}
- GetValue={() => StrCast((this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle!])}
- SetValue={(value: string) => ((this.layoutDoc.isTemplate ? this.layoutDoc : Doc.GetProto(this.layoutDoc))[showTitle!] = value) ? true : true}
- />
- </div>
- }
- {!showCaption ? (null) :
- <div style={{ position: "absolute", bottom: 0, transformOrigin: "bottom left", width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
- <FormattedTextBox {...this.props} onClick={this.onClickHandler} DataDoc={this.dataDoc} active={returnTrue} isSelected={this.isSelected} focus={emptyFunction} select={this.select} fieldExt={""} hideOnLeave={true} fieldKey={showCaption} />
+ <div>
+ <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
+ ref={this._mainCont}
+ style={{
+ pointerEvents: this.layoutDoc.isBackground && !this.isSelected() ? "none" : "all",
+ color: foregroundColor,
+ outlineColor: ["transparent", "maroon", "maroon"][brushDegree],
+ outlineStyle: ["none", "dashed", "solid"][brushDegree],
+ outlineWidth: brushDegree && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+ `${brushDegree * this.props.ScreenToLocalTransform().Scale}px` : "0px",
+ marginLeft: brushDegree && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+ `${-brushDegree * this.props.ScreenToLocalTransform().Scale}px` : undefined,
+ marginTop: brushDegree && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+ `${-brushDegree * this.props.ScreenToLocalTransform().Scale}px` : undefined,
+ border: brushDegree && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+ `${["none", "dashed", "solid"][brushDegree]} ${["transparent", "maroon", "maroon"][brushDegree]} ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
+ borderRadius: "inherit",
+ background: backgroundColor,
+ width: nativeWidth,
+ height: nativeHeight,
+ transform: `scale(${this.props.ContentScaling()})`,
+ opacity: this.Document.opacity
+ }}
+ onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
+ onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
+ >
+ {!showTitle && !showCaption ? this.contents :
+ <div style={{ position: "absolute", display: "inline-block", width: "100%", height: "100%", pointerEvents: "none" }}>
+
+ <div style={{ width: "100%", height: showTextTitle ? "calc(100% - 33px)" : "100%", display: "inline-block", position: "absolute", top: showTextTitle ? "29px" : undefined }}>
+ {this.contents}
</div>
- }
- </div>
- }
+ {!showTitle ? (null) :
+ <div style={{
+ position: showTextTitle ? "relative" : "absolute", top: 0, padding: "4px", textAlign: "center", textOverflow: "ellipsis", whiteSpace: "pre",
+ pointerEvents: SelectionManager.GetIsDragging() ? "none" : "all",
+ overflow: "hidden", width: `${100 * this.props.ContentScaling()}%`, height: 25, background: "rgba(0, 0, 0, .4)", color: "white",
+ transformOrigin: "top left", transform: `scale(${1 / this.props.ContentScaling()})`
+ }}>
+ <EditableView
+ contents={(this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle]}
+ display={"block"}
+ height={72}
+ fontSize={12}
+ GetValue={() => StrCast((this.layoutDoc.isTemplate || !this.dataDoc ? this.layoutDoc : this.dataDoc)[showTitle!])}
+ SetValue={(value: string) => (Doc.GetProto(this.layoutDoc)[showTitle!] = value) ? true : true}
+ />
+ </div>
+ }
+ {!showCaption ? (null) :
+ <div style={{ position: "absolute", bottom: 0, transformOrigin: "bottom left", width: `${100 * this.props.ContentScaling()}%`, transform: `scale(${1 / this.props.ContentScaling()})` }}>
+ <FormattedTextBox {...this.props} DataDoc={this.dataDoc} active={returnTrue} isSelected={this.isSelected} focus={emptyFunction} select={this.select} selectOnLoad={this.props.selectOnLoad} fieldExt={""} hideOnLeave={true} fieldKey={showCaption} />
+ </div>
+ }
+ </div>
+ }
+ </div>
</div>
);
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 6fc94a140..ec35465eb 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,6 +1,6 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import { faEye } from '@fortawesome/free-regular-svg-icons';
-import { faAsterisk, faFileAudio, faImage, faPaintBrush } from '@fortawesome/free-solid-svg-icons';
+import { faAsterisk, faFileAudio, faImage, faPaintBrush, faBrain } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, runInAction } from 'mobx';
import { observer } from "mobx-react";
@@ -28,12 +28,15 @@ 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');
-library.add(faImage, faEye as any, faPaintBrush);
+library.add(faImage, faEye as any, faPaintBrush, faBrain);
library.add(faFileAudio, faAsterisk);
@@ -219,6 +222,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
let modes: ContextMenuProps[] = [];
modes.push({ description: "Generate Tags", event: this.generateMetadata, icon: "tag" });
modes.push({ description: "Find Faces", event: this.extractFaces, icon: "camera" });
+ modes.push({ description: "Recommend", event: this.extractText, icon: "brain" });
ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs, icon: "asterisk" });
ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes, icon: "eye" });
@@ -236,6 +240,22 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
}
}
+ 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) => {
let converter = (results: any) => {
let tagDoc = new Doc;