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.tsx173
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts69
-rw-r--r--src/client/util/SearchUtil.ts21
-rw-r--r--src/client/views/MainView.tsx7
-rw-r--r--src/client/views/SearchBox.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx24
-rw-r--r--src/client/views/nodes/ImageBox.tsx11
8 files changed, 314 insertions, 5 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..ddaa8a7fc
--- /dev/null
+++ b/src/client/ClientRecommender.tsx
@@ -0,0 +1,173 @@
+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
+ 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.Manager.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..954a05585 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,67 @@ 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);
+ },
+ analyzer: async (target: Doc, keys: string[], data: string, converter: Converter) => {
+ let results = await ExecuteQuery<string, any>(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);
+ }
+ };
+ 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);
+ });
+ }
+
+ }
+
} \ No newline at end of file
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index ee5a83710..3a3ba1803 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -77,4 +77,23 @@ 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: {
+ query
+ }
+ });
+ let res: string[] = JSON.parse(response);
+ const fields = await DocServer.GetRefFields(res);
+ const docs: Doc[] = [];
+ for (const id of res) {
+ const field = fields[id];
+ if (field instanceof Doc) {
+ docs.push(field);
+ }
+ }
+ return docs;
+ }
+}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 031478477..419b15697 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -39,6 +39,7 @@ import { FilterBox } from './search/FilterBox';
import { CollectionTreeView } from './collections/CollectionTreeView';
import { ClientUtils } from '../util/ClientUtils';
import { SchemaHeaderField, RandomPastel } from '../../new_fields/SchemaHeaderField';
+//import { DocumentManager } from '../util/DocumentManager';
import { DictationManager } from '../util/DictationManager';
@observer
@@ -514,6 +515,12 @@ export class MainView extends React.Component {
</div >;
}
+ // clusterDocuments = () => {
+ // DocumentManager.Instance.DocumentViews();
+ // }
+
+
+
@action
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index 33cb63df5..17f99fa05 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -47,7 +47,7 @@ export class SearchBox extends React.Component {
}
@action
- getResults = async (query: string) => {
+ public getResults = async (query: string) => {
let response = await rp.get(Utils.prepend('/search'), {
qs: {
query
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ba8dcff98..e9791df4e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -36,6 +36,11 @@ 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 { DocumentType, Docs } from "../../../documents/Documents";
import { RouteStore } from "../../../../server/RouteStore";
import { string, number, elementType } from "prop-types";
@@ -775,6 +780,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 () => {
@@ -881,6 +887,23 @@ 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));
+ 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);
+ }));
+ console.log(ClientRecommender.Instance.createDistanceMatrix());
+ },
+ icon: "brain"
+ });
}
@@ -989,6 +1012,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/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index ca0f637eb..73b892e26 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";
@@ -33,7 +33,7 @@ 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);
@@ -222,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" });
@@ -239,6 +240,12 @@ 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));
+ }
+
generateMetadata = (threshold: Confidence = Confidence.Excellent) => {
let converter = (results: any) => {
let tagDoc = new Doc;