diff options
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/ClientRecommender.scss | 12 | ||||
| -rw-r--r-- | src/client/ClientRecommender.tsx | 173 | ||||
| -rw-r--r-- | src/client/cognitive_services/CognitiveServices.ts | 69 | ||||
| -rw-r--r-- | src/client/util/SearchUtil.ts | 21 | ||||
| -rw-r--r-- | src/client/views/MainView.tsx | 7 | ||||
| -rw-r--r-- | src/client/views/SearchBox.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 24 | ||||
| -rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 11 |
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; |
