diff options
Diffstat (limited to 'src/client')
| -rw-r--r-- | src/client/ClientRecommender.scss | 12 | ||||
| -rw-r--r-- | src/client/ClientRecommender.tsx | 175 | ||||
| -rw-r--r-- | src/client/cognitive_services/CognitiveServices.ts | 75 | ||||
| -rw-r--r-- | src/client/util/SearchUtil.ts | 25 | ||||
| -rw-r--r-- | src/client/views/MainView.tsx | 10 | ||||
| -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 | 29 | ||||
| -rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 138 | ||||
| -rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 24 |
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; |
